current work version

Dieser Commit ist enthalten in:
Sebastian Tobie 2025-06-18 21:47:24 +02:00
Ursprung a828ccfffc
Commit 0080f592c0
18 geänderte Dateien mit 375 neuen und 268 gelöschten Zeilen

Datei anzeigen

@ -4,5 +4,26 @@ RUST_LOG="TRACE"
SSL_CERT_DIR="/etc/ca-certificates/extracted/cadir/" SSL_CERT_DIR="/etc/ca-certificates/extracted/cadir/"
SSL_CERT_FILE="/etc/ca-certificates/extracted/tls-ca-bundle.pem" SSL_CERT_FILE="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
[target] [profile.dev]
[target.'cfg(debug_assertions)'] opt-level = 3
debug = true
strip = "none"
debug-assertions = true
overflow-checks = true
lto = false
panic = "abort"
incremental = true
codegen-units = 16
rpath = false
[profile.release]
opt-level = 3
debug = false
strip = "debuginfo"
debug-assertions = false
overflow-checks = true
lto = true
panic = 'abort'
incremental = true
codegen-units = 16
rpath = false

Datei anzeigen

@ -10,3 +10,6 @@ end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = false trim_trailing_whitespace = false
insert_final_newline = false insert_final_newline = false
[*.{yml,yaml}]
indent_size = 2

Datei anzeigen

@ -52,3 +52,11 @@ repos:
types: types:
- rust - rust
pass_filenames: false pass_filenames: false
- id: tomlq
name: toml Format
description: Formats Toml files
entry: echo tomlq . -ti
language: system
types:
- toml
exclude: '\.lock$'

47
Cargo.lock generiert
Datei anzeigen

@ -190,17 +190,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "async-scoped"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740"
dependencies = [
"futures",
"pin-project",
"tokio",
]
[[package]] [[package]]
name = "async-signal" name = "async-signal"
version = "0.2.11" version = "0.2.11"
@ -244,9 +233,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
@ -1096,9 +1085,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.173" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libsystemd" name = "libsystemd"
@ -1529,16 +1518,15 @@ dependencies = [
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "5.2.0" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "racme" name = "racme"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"acme2-eab", "acme2-eab",
"async-scoped",
"caps", "caps",
"clap", "clap",
"data-encoding", "data-encoding",
@ -1722,9 +1710,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.27" version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"ring", "ring",
@ -1953,12 +1941,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@ -2266,9 +2251,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.29" version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2724,18 +2709,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

Datei anzeigen

@ -12,9 +12,18 @@ unstable = ["capabilities"]
derive-new = "0.7.0" derive-new = "0.7.0"
env_logger = "0.11" env_logger = "0.11"
lazy_static = "1.5" lazy_static = "1.5"
schemars = { version = "0.9.0", default-features = false, features = ["derive", "std", "preserve_order"] }
serde_json = { version = "1.0.140", default-features = false, features = ["std"] }
toml = "0.8" toml = "0.8"
[dependencies.schemars]
version = "0.9.0"
default-features = false
features = ["derive", "std", "preserve_order"]
[dependencies.serde_json]
version = "1.0.140"
default-features = false
features = ["std"]
[dependencies.caps] [dependencies.caps]
version = "0.5.5" version = "0.5.5"
default-features = false default-features = false
@ -23,7 +32,7 @@ optional = true
[dependencies.libc] [dependencies.libc]
version = "0.2" version = "0.2"
default-features = false default-features = false
features = ["const-extern-fn", "std", "extra_traits"] features = ["const-extern-fn", "std"]
[dependencies.data-encoding] [dependencies.data-encoding]
version = "2.9" version = "2.9"
@ -52,11 +61,6 @@ version = "0.1"
default-features = false default-features = false
features = ["fs"] features = ["fs"]
[dependencies.async-scoped]
version = "0.9"
default-features = false
features = ["use-tokio"]
[dependencies.libsystemd] [dependencies.libsystemd]
version = "0.7" version = "0.7"
default-features = false default-features = false
@ -82,7 +86,7 @@ features = ["derive"]
[dependencies.tokio] [dependencies.tokio]
version = "1.45" version = "1.45"
default-features = false default-features = false
features = ["rt", "sync", "time", "net"] features = ["rt", "sync", "time", "net", "macros"]
[dependencies.reqwest] [dependencies.reqwest]
version = "0.12" version = "0.12"

Datei anzeigen

@ -1,10 +0,0 @@
POLYFILL=polyfill-glibc
TARGET_GLIBC=2.36
SOURCEFILES=$(wildcard src/**/*.rs)
target/release/racme: Cargo.toml Cargo.lock $(SOURCEFILES)
cargo build --release
racme: target/release/racme
$(POLYFILL) --target-glibc=$(TARGET_GLIBC) --rename-dynamic-symbols=polyfill-glibc-renames.txt --output=racme $^
strip racme

Datei anzeigen

@ -1 +0,0 @@
pidfd_getpid@GLIBC_2.39 pidfd_getpid

Datei anzeigen

@ -8,13 +8,16 @@ use lazy_static::lazy_static;
lazy_static! { lazy_static! {
pub static ref FILE_MODE: OpenOptions = OpenOptions::new().create(false).read(true).write(false).truncate(false).to_owned(); pub static ref FILE_MODE: OpenOptions = OpenOptions::new().create(false).read(true).write(false).truncate(false).to_owned();
pub static ref FILE_MODE_WRITE: OpenOptions = OpenOptions::new().create_new(true).write(true).to_owned(); pub static ref FILE_MODE_WRITE: OpenOptions = OpenOptions::new().create_new(true).write(true).to_owned();
pub static ref FILE_MODE_OVERWRITE: OpenOptions = OpenOptions::new().create_new(false).create(true).truncate(true).write(true).to_owned();
}
lazy_static! {
pub static ref LETS_ENCRYPT: String = String::from("letsencrypt"); pub static ref LETS_ENCRYPT: String = String::from("letsencrypt");
pub static ref LETS_ENCRYPT_STAGING: String = String::from("letsencrypt-staging"); pub static ref LETS_ENCRYPT_STAGING: String = String::from("letsencrypt-staging");
} }
pub fn with_mode_write(mode: u32) -> OpenOptions { pub const MODE_SECRETS: u32 = 0o600;
FILE_MODE.clone().mode(mode).to_owned() pub const MODE_PRIVATE: u32 = 0o640;
} pub const MODE_PUBLIC: u32 = 0o644;
pub const POOL_SIZE: usize = 1; pub const POOL_SIZE: usize = 1;

Datei anzeigen

@ -12,8 +12,6 @@ pub(crate) mod process;
pub(crate) mod types; pub(crate) mod types;
pub(crate) mod utils; pub(crate) mod utils;
#[cfg(feature = "capabilities")]
use crate::utils::check_permissions;
use crate::{ use crate::{
consts::*, consts::*,
prelude::*, prelude::*,
@ -30,9 +28,9 @@ use crate::{
SubCommand, SubCommand,
}, },
}, },
utils::check_permissions,
}; };
use acme2_eab::Directory; use acme2_eab::Directory;
use async_scoped::TokioScope;
use clap::Parser; use clap::Parser;
use env_logger::init as log_init; use env_logger::init as log_init;
use libsystemd::daemon; use libsystemd::daemon;
@ -112,7 +110,7 @@ async fn load_privkey(path: PathBuf) -> Result<PKey<Private>, Error> {
async fn racme(flags: Arguments) -> Result<(), Error> { async fn racme(flags: Arguments) -> Result<(), Error> {
let client = default_client()?; let client = default_client()?;
let dns_manager = Manager::new(); let mut dns_manager = Manager::new();
let systemd_access = daemon::booted(); let systemd_access = daemon::booted();
let mainconfig = { let mainconfig = {
let file = match FILE_MODE.open(flags.config).await { let file = match FILE_MODE.open(flags.config).await {
@ -123,6 +121,9 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
}; };
GeneralConfig::from_file(file).await GeneralConfig::from_file(file).await
}; };
for (zone, builder) in mainconfig.dns.iter() {
dns_manager.add_builder(zone.clone(), builder.clone()).await;
}
trace!("Parsed Config: {mainconfig:?}"); trace!("Parsed Config: {mainconfig:?}");
let files = { let files = {
let rd = match read_dir(mainconfig.sites_path.clone()).await { let rd = match read_dir(mainconfig.sites_path.clone()).await {
@ -171,27 +172,23 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
} }
let challengepath = mainconfig.http_challenge_path.and_then(|path| PathBuf::from_str(path.as_str()).ok()); let challengepath = mainconfig.http_challenge_path.and_then(|path| PathBuf::from_str(path.as_str()).ok());
unsafe {
TokioScope::scope_and_collect(|scope| {
for site in siteconfigs { for site in siteconfigs {
if let Some(account) = accounts.get(&site.ca) { if let Some(account) = accounts.get(&site.ca) {
scope.spawn(process::site(ProcessorArgs::new( process::site(ProcessorArgs::new(
site, site,
Arc::clone(account), account.clone(),
&reload_services, &reload_services,
&restart_services, &restart_services,
certs.clone(), certs.clone(),
challengepath.clone(), challengepath.clone(),
dns_manager.clone(), dns_manager.clone(),
client.clone(), client.clone(),
))); ))
.await;
} else { } else {
error!("Could not process site {} because of previous errors", site.name) error!("Could not process site {} because of previous errors", site.name)
} }
} }
})
}
.await;
if systemd_access { if systemd_access {
process::services(restart_services.into_inner(), reload_services.into_inner()).await; process::services(restart_services.into_inner(), reload_services.into_inner()).await;
@ -242,7 +239,6 @@ async fn schema_generator() -> Result<(), Error> {
fn main() { fn main() {
log_init(); log_init();
let args = Arguments::parse(); let args = Arguments::parse();
#[cfg(feature = "capabilities")]
if args.subcommands.is_none() && !check_permissions() { if args.subcommands.is_none() && !check_permissions() {
error!( error!(
"This program needs the capability to change the ownership and the permissions of files. this can be done via adding the capability via `capsh --caps=\"cap_chown+ep cap_fowner+ep\" --shell=racme -- racme.toml`, systemd service setting AmbientCapabilities or running as root(not recommended)" "This program needs the capability to change the ownership and the permissions of files. this can be done via adding the capability via `capsh --caps=\"cap_chown+ep cap_fowner+ep\" --shell=racme -- racme.toml`, systemd service setting AmbientCapabilities or running as root(not recommended)"

Datei anzeigen

@ -1,27 +1,29 @@
#[cfg(feature = "capabilities")]
use std::os::{
fd::AsFd,
unix::fs::fchown,
};
use std::{ use std::{
collections::{ collections::{
HashMap, HashMap,
HashSet, HashSet,
}, },
fs::Permissions, fs::Permissions,
os::unix::fs::PermissionsExt as _, os::{
fd::AsFd,
unix::fs::{
PermissionsExt as _,
fchown,
},
},
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
}; };
#[cfg(feature = "capabilities")]
use crate::utils::get_uid_gid;
use crate::{ use crate::{
consts::{ consts::{
ATTEMPTS, ATTEMPTS,
FILE_MODE, FILE_MODE,
FILE_MODE_OVERWRITE,
MODE_PRIVATE,
MODE_PUBLIC,
MODE_SECRETS,
WAIT_TIME, WAIT_TIME,
with_mode_write,
}, },
load_privkey, load_privkey,
prelude::*, prelude::*,
@ -38,6 +40,7 @@ use crate::{
}, },
utils::{ utils::{
gen_key, gen_key,
get_uid_gid,
prefix_emails, prefix_emails,
}, },
}; };
@ -54,7 +57,6 @@ use acme2_eab::{
OrderBuilder, OrderBuilder,
OrderStatus, OrderStatus,
}; };
use async_scoped::TokioScope;
use log::*; use log::*;
use openssl::{ use openssl::{
hash::MessageDigest, hash::MessageDigest,
@ -82,6 +84,7 @@ use tokio::{
AsyncReadExt, AsyncReadExt,
AsyncWriteExt, AsyncWriteExt,
}, },
join,
}; };
use zbus_systemd::systemd1; use zbus_systemd::systemd1;
@ -146,7 +149,7 @@ pub async fn accounts(
} }
} else { } else {
info!("creating new key for the account {}", name.clone()); info!("creating new key for the account {}", name.clone());
accountkeyfile = match with_mode_write(0o600).open(accountkey).await { accountkeyfile = match FILE_MODE_OVERWRITE.clone().mode(MODE_SECRETS).open(accountkey).await {
Ok(file) => Some(file), Ok(file) => Some(file),
Err(error) => { Err(error) => {
error!("Failed to open the file for the accountkey: {error}"); error!("Failed to open the file for the accountkey: {error}");
@ -218,7 +221,6 @@ pub async fn site(args: ProcessorArgs<'_>) {
}; };
cert_renew = true; cert_renew = true;
} }
#[cfg(feature = "capabilities")]
let (uid, gid) = get_uid_gid(args.owner(), args.group()); let (uid, gid) = get_uid_gid(args.owner(), args.group());
let mut private_key; let mut private_key;
// Private key block // Private key block
@ -245,7 +247,7 @@ pub async fn site(args: ProcessorArgs<'_>) {
} }
if write_pkey { if write_pkey {
let pkey = private_key.private_key_to_pem_pkcs8().unwrap(); let pkey = private_key.private_key_to_pem_pkcs8().unwrap();
let mut file = match_error!(with_mode_write(0o640).open(private_key_file.clone()).await=>Err(error)->"Failed to write new private key: {error}"); let mut file = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PRIVATE).open(private_key_file.clone()).await=>Err(error)->"Failed to write new private key: {error}");
#[cfg(feature = "capabilities")] #[cfg(feature = "capabilities")]
if let Err(error) = fchown(file.as_fd(), uid, gid) { if let Err(error) = fchown(file.as_fd(), uid, gid) {
error!("Failed to change owner of the new privatekey: {error}"); error!("Failed to change owner of the new privatekey: {error}");
@ -289,13 +291,11 @@ pub async fn site(args: ProcessorArgs<'_>) {
builder.set_identifiers(args.san().iter().map(|s| s.to_owned().into()).collect::<Vec<Identifier>>()); builder.set_identifiers(args.san().iter().map(|s| s.to_owned().into()).collect::<Vec<Identifier>>());
order = match_error!(builder.build().await=>Err(error)-> "Failed order the certificate: {error}"); order = match_error!(builder.build().await=>Err(error)-> "Failed order the certificate: {error}");
let authorizations = match_error!(order.authorizations().await=>Err(error)-> "Failed to get the authorizations: {error}"); let authorizations = match_error!(order.authorizations().await=>Err(error)-> "Failed to get the authorizations: {error}");
let (_, result) = tokio::join! { let (_, result) = join! {
unsafe { async {
TokioScope::scope_and_collect(|scope|{
for authorization in authorizations { for authorization in authorizations {
scope.spawn(auth(authorization, args.challenge_dir(), args.dns_manager())); auth(authorization, args.challenge_dir(), args.dns_manager()).await;
} }
})
}, },
order.wait_ready(WAIT_TIME, ATTEMPTS), order.wait_ready(WAIT_TIME, ATTEMPTS),
}; };
@ -338,17 +338,13 @@ pub async fn site(args: ProcessorArgs<'_>) {
Ok(Some(certs)) => certs, Ok(Some(certs)) => certs,
}; };
debug!("Received {} certificates.", certs.len()); debug!("Received {} certificates.", certs.len());
let mut pubkey_file = let mut pubkey_file = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PUBLIC).open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
match_error!(with_mode_write(0o644).open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
match_error!(pubkey_file.write_all(&certs[0].to_pem().unwrap()).await=>Err(error)-> "Failed to write the publickey: {error}"); match_error!(pubkey_file.write_all(&certs[0].to_pem().unwrap()).await=>Err(error)-> "Failed to write the publickey: {error}");
#[cfg(feature = "capabilities")]
if let Err(error) = fchown(pubkey_file.as_fd(), uid, gid) { if let Err(error) = fchown(pubkey_file.as_fd(), uid, gid) {
error!("Failed to change owner of the new publickey: {error}"); error!("Failed to change owner of the new publickey: {error}");
return; return;
} }
let mut fullchain = let mut fullchain = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PUBLIC).open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
match_error!(with_mode_write(0o644).open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
#[cfg(feature = "capabilities")]
if let Err(error) = fchown(fullchain.as_fd(), uid, gid) { if let Err(error) = fchown(fullchain.as_fd(), uid, gid) {
error!("Failed to change owner of the new file with the complete chain: {error}"); error!("Failed to change owner of the new file with the complete chain: {error}");
return; return;
@ -356,8 +352,8 @@ pub async fn site(args: ProcessorArgs<'_>) {
for cert in certs.clone() { for cert in certs.clone() {
let _ = fullchain.write_all(&cert.to_pem().unwrap()).await; let _ = fullchain.write_all(&cert.to_pem().unwrap()).await;
} }
let mut bundle = match_error!(with_mode_write(0o640).open(directory.join("bundle.pem")).await=>Err(error)-> "failed to open the bundle.pem: {error}"); let mut bundle = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PRIVATE).open(directory.join("bundle.pem")).await=>Err(error)-> "failed to open the bundle.pem: {error}");
#[cfg(feature = "capabilities")]
if let Err(error) = fchown(bundle.as_fd(), uid, gid) { if let Err(error) = fchown(bundle.as_fd(), uid, gid) {
error!("Failed to change owner of the new bundle: {error}"); error!("Failed to change owner of the new bundle: {error}");
return; return;
@ -377,7 +373,7 @@ pub async fn site(args: ProcessorArgs<'_>) {
} }
} }
async fn dns_auth(mut dns_challenge: Challenge, manager: Manager) -> types::Result<()> { async fn dns_auth(mut dns_challenge: Challenge, manager: Manager, domain: String) -> types::Result<()> {
let token = match dns_challenge.token { let token = match dns_challenge.token {
Some(ref token) => token.clone(), Some(ref token) => token.clone(),
None => { None => {
@ -389,13 +385,13 @@ async fn dns_auth(mut dns_challenge: Challenge, manager: Manager) -> types::Resu
Ok(None) => return Error::err("Failed to get key authoriration: No Authorization returned"), Ok(None) => return Error::err("Failed to get key authoriration: No Authorization returned"),
Err(error) => return Error::err(format!("Failed to get key_authorization: {error}")), Err(error) => return Error::err(format!("Failed to get key_authorization: {error}")),
}; };
if let Some(_guard) = manager.set_record(token.clone(), value).await { if let Some(mut guard) = manager.set_record(format!("_acme-challenge.{domain}"), value).await {
dns_challenge = match dns_challenge.validate().await { dns_challenge = match dns_challenge.validate().await {
Ok(challenge) => challenge, Ok(challenge) => challenge,
Err(error) => return Error::err(format!("Failed to send the request for validation: {error}")), Err(error) => return Error::err(format!("Failed to send the request for validation: {error}")),
}; };
let challenge_result = dns_challenge.wait_done(WAIT_TIME, ATTEMPTS).await; let challenge_result = dns_challenge.wait_done(WAIT_TIME, ATTEMPTS).await;
drop(_guard); guard.unset_token().await;
match challenge_result { match challenge_result {
Ok(_) => { Ok(_) => {
info!("Challenge: {token} was correctly validated"); info!("Challenge: {token} was correctly validated");
@ -410,8 +406,9 @@ async fn dns_auth(mut dns_challenge: Challenge, manager: Manager) -> types::Resu
pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, manager: Manager) { pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, manager: Manager) {
trace!("processing Authorization: {}:{}", auth.identifier.r#type, auth.identifier.value.clone());
if let Some(dns_challenge) = auth.get_challenge("dns-01") { if let Some(dns_challenge) = auth.get_challenge("dns-01") {
match dns_auth(dns_challenge, manager).await { match dns_auth(dns_challenge, manager, auth.identifier.value.clone()).await {
Ok(()) => return, Ok(()) => return,
Err(error) => { Err(error) => {
error!("Failed to authenticate via DNS: {error}"); error!("Failed to authenticate via DNS: {error}");
@ -426,8 +423,7 @@ pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, manager:
if let Some(directory) = challenge_dir { if let Some(directory) = challenge_dir {
match_error!(create_dir_all(directory.clone()).await=>Err(error)-> "Failed to ensure the directory exists: {error}"); match_error!(create_dir_all(directory.clone()).await=>Err(error)-> "Failed to ensure the directory exists: {error}");
let filename = directory.join(challenge.token.clone().unwrap()); let filename = directory.join(challenge.token.clone().unwrap());
let mut challengefile = let mut challengefile = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PUBLIC).open(filename.clone()).await=>Err(error)-> "Failed to open the file for the http-challenge: {error}");
match_error!(with_mode_write(0o644).open(filename.clone()).await=>Err(error)-> "Failed to open the file for the http-challenge: {error}");
match_error!(challengefile.set_permissions(Permissions::from_mode(0o644)).await=>Err(error)-> "Failed to give the file the nessesary permissions: {error}"); match_error!(challengefile.set_permissions(Permissions::from_mode(0o644)).await=>Err(error)-> "Failed to give the file the nessesary permissions: {error}");
match_error!( match_error!(
challengefile.write_all(challenge.key_authorization().unwrap().unwrap().as_bytes()).await=>Err(error)-> challengefile.write_all(challenge.key_authorization().unwrap().unwrap().as_bytes()).await=>Err(error)->

Datei anzeigen

@ -33,10 +33,15 @@ pub struct GeneralConfig {
pub sites_path: String, pub sites_path: String,
#[serde(default = "GeneralConfig::default_challenge")] #[serde(default = "GeneralConfig::default_challenge")]
pub http_challenge_path: Option<String>, pub http_challenge_path: Option<String>,
/// This contains the domains(Keys) and the DNS-Servers(values) that are responsible for it.
#[serde(default = "GeneralConfig::default_dns")] #[serde(default = "GeneralConfig::default_dns")]
pub dns: HashMap<String, DnsBuilder>, pub dns: HashMap<String, DnsBuilder>,
#[serde(default = "GeneralConfig::default_certificates")] #[serde(default = "GeneralConfig::default_certificates")]
pub certificates_path: String, pub certificates_path: String,
/// The Key of this table describe an nickname for an CA.
/// Letsencrypt Prod and Staging are builtin configured, so they doesn't have to be configured.
#[serde(default = "GeneralConfig::default_cas")] #[serde(default = "GeneralConfig::default_cas")]
pub ca: HashMap<String, CA>, pub ca: HashMap<String, CA>,
} }
@ -166,11 +171,9 @@ pub struct SiteConfig {
/// Owner of the Certificate and private key /// Owner of the Certificate and private key
#[serde(default)] #[serde(default)]
#[cfg(feature = "capabilities")] pub owner: Option<String>,
pub owner: String,
/// Group of the Certificate and private key /// Group of the Certificate and private key
#[serde(default)] #[serde(default)]
#[cfg(feature = "capabilities")] pub group: Option<String>,
pub group: String,
} }

Datei anzeigen

@ -4,9 +4,10 @@ use serde::Deserialize;
use crate::{ use crate::{
macros::DefDer, macros::DefDer,
types::traits::{ types::{
DnsHandler, dns::Dns,
DnsToken, structs::DnsToken,
traits::DnsHandler,
}, },
}; };
@ -15,22 +16,26 @@ use crate::{
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DNSUpdateClientOptions {} pub struct DNSUpdateClientOptions {}
impl DNSUpdateClientOptions {
pub fn build(self, _zone: String) -> Dns {
Dns::DNSUpdate(DnsUpdateHandler {})
}
}
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
pub(super) struct DnsUpdateHandler {} pub struct DnsUpdateHandler {}
impl DnsHandler for DnsUpdateHandler { impl DnsHandler for DnsUpdateHandler {
async fn set_record(&self, _domain: String, _content: String) -> crate::types::BoxedResult<dyn DnsToken> { async fn set_record(&self, _domain: String, _content: String) -> crate::types::Result<DnsToken> {
Ok(Box::pin(DnsUpdateToken {})) Ok(DnsToken::new_dns_update())
} }
} }
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
struct DnsUpdateToken {} pub struct DnsUpdateToken {}
impl Drop for DnsUpdateToken { impl DnsUpdateToken {
fn drop(&mut self) { pub async fn remove(&mut self) {}
todo!()
} }
}
impl DnsToken for DnsUpdateToken {}
unsafe impl Send for DnsUpdateToken {} unsafe impl Send for DnsUpdateToken {}

Datei anzeigen

@ -14,11 +14,11 @@ use crate::{
PdnsHandler, PdnsHandler,
}, },
}, },
structs::Error, structs::{
traits::{
DnsHandler,
DnsToken, DnsToken,
Error,
}, },
traits::DnsHandler,
}, },
}; };
use log::*; use log::*;
@ -27,7 +27,6 @@ use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
collections::HashMap, collections::HashMap,
pin::Pin,
sync::Arc, sync::Arc,
}; };
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -42,7 +41,7 @@ impl Manager {
}))) })))
} }
pub async fn set_record(&self, domain: String, value: String) -> Option<Pin<Box<dyn DnsToken + '_>>> { pub async fn set_record(&self, domain: String, value: String) -> Option<DnsToken> {
let mut tld = domain.clone(); let mut tld = domain.clone();
if !tld.ends_with('.') { if !tld.ends_with('.') {
tld.push('.'); tld.push('.');
@ -50,8 +49,8 @@ impl Manager {
let (mut best_match_domain, mut best_match_length) = ("", 0); let (mut best_match_domain, mut best_match_length) = ("", 0);
let guard = self.0.lock().await; let guard = self.0.lock().await;
for domain in guard.servers.keys() { for domain in guard.servers.keys() {
if domain.ends_with(&tld) { trace!("Checking {domain} < {tld}");
let matched = domain.rmatches(&tld).last().unwrap(); if let Some(matched) = tld.rmatches(domain).last() {
if matched.len() > best_match_length { if matched.len() > best_match_length {
best_match_domain = matched; best_match_domain = matched;
best_match_length = matched.len(); best_match_length = matched.len();
@ -59,6 +58,7 @@ impl Manager {
} }
} }
if best_match_length == 0 { if best_match_length == 0 {
trace!("No matching Domain Found: {domain} TXT {value}");
return None; return None;
} }
let handler = guard.servers.get(best_match_domain).unwrap(); let handler = guard.servers.get(best_match_domain).unwrap();
@ -70,6 +70,21 @@ impl Manager {
}, },
} }
} }
pub async fn add_builder(&mut self, zone: String, builder: DnsBuilder) {
let mut fixed_zone = zone.clone();
if !fixed_zone.ends_with('.') {
fixed_zone.push('.');
}
self.0.lock().await.servers.insert(
fixed_zone,
match builder {
DnsBuilder::PowerDNS(pdns_client_options) => pdns_client_options.build(zone),
DnsBuilder::DNSUpdate(dnsupdate_client_options) => dnsupdate_client_options.build(zone),
DnsBuilder::None => Dns::None,
},
);
}
} }
unsafe impl Send for Manager {} unsafe impl Send for Manager {}
@ -92,14 +107,14 @@ pub enum DnsBuilder {
} }
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
enum Dns { pub enum Dns {
PowerDNS(PdnsHandler), PowerDNS(PdnsHandler),
DNSUpdate(DnsUpdateHandler), DNSUpdate(DnsUpdateHandler),
None, None,
} }
impl Dns { impl Dns {
pub async fn set_record(&self, domain: String, content: String) -> crate::types::BoxedResult<dyn DnsToken> { pub async fn set_record(&self, domain: String, content: String) -> crate::types::Result<DnsToken> {
match self { match self {
Dns::PowerDNS(pdns_handler) => pdns_handler.set_record(domain, content).await, Dns::PowerDNS(pdns_handler) => pdns_handler.set_record(domain, content).await,
Dns::DNSUpdate(dns_update_handler) => dns_update_handler.set_record(domain, content).await, Dns::DNSUpdate(dns_update_handler) => dns_update_handler.set_record(domain, content).await,

Datei anzeigen

@ -1,9 +1,6 @@
use std::{ use std::time::{
pin::Pin,
time::{
SystemTime, SystemTime,
UNIX_EPOCH, UNIX_EPOCH,
},
}; };
use derive_new::new; use derive_new::new;
@ -12,22 +9,24 @@ use macro_rules_attribute::macro_rules_derive;
use reqwest::{ use reqwest::{
RequestBuilder, RequestBuilder,
StatusCode, StatusCode,
Url,
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{ use serde::{
Deserialize, Deserialize,
Serialize, Serialize,
}; };
use tokio::runtime::Handle;
use crate::{ use crate::{
default_client,
macros::DefDer, macros::DefDer,
types::{ types::{
structs::Error, dns::Dns,
traits::{ structs::{
DnsHandler,
DnsToken, DnsToken,
Error,
}, },
traits::DnsHandler,
}, },
}; };
@ -48,8 +47,20 @@ impl PdnsClientOptions {
} }
} }
impl PdnsClientOptions {
pub fn build(self, zone: String) -> Dns {
Dns::PowerDNS(PdnsHandler {
client: default_client().unwrap(),
server: self.server,
api_key: self.api_key,
server_id: self.server_id,
zone,
})
}
}
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
pub(super) struct PdnsHandler { pub struct PdnsHandler {
client: reqwest::Client, client: reqwest::Client,
server: String, server: String,
api_key: String, api_key: String,
@ -57,24 +68,49 @@ pub(super) struct PdnsHandler {
zone: String, zone: String,
} }
fn fix_url(baseurl: Url, path: String) -> Result<Url, ()> {
let mut copy = baseurl.clone();
copy.set_scheme("https")?;
copy.set_port(None)?;
copy.set_path(&path);
copy.set_query(None);
copy.set_fragment(None);
copy.set_username("")?;
copy.set_password(None)?;
trace!("Resulting URL: {copy}");
Ok(copy)
}
impl DnsHandler for PdnsHandler { impl DnsHandler for PdnsHandler {
async fn set_record(&self, domain: String, content: String) -> crate::types::BoxedResult<dyn DnsToken> { async fn set_record(&self, mut domain: String, content: String) -> crate::types::Result<DnsToken> {
let base_request = self trace!("Original URL: {}", self.server);
.client let baseurl = match reqwest::Url::parse(&self.server) {
.patch(format!("https://{}/api/v1/servers/{}/zones/{}", self.server, self.server_id, self.zone)) Ok(url) => {
.header("Content-Type", "application/json") match fix_url(url, format!("/api/v1/servers/{}/zones/{}", self.server_id, self.zone)) {
.header("X-API-Key", self.api_key.clone()); Ok(url) => url,
Err(_) => return Error::err("Failed to parse the Server URL of the DNS Server"),
}
},
Err(error) => return Error::err(format!("Failed to parse the server url: {error}")),
};
let base_request = self.client.patch(baseurl).header("Content-Type", "application/json").header("X-API-Key", self.api_key.clone());
if !domain.ends_with('.') {
domain.push('.');
}
match base_request match base_request
.try_clone() .try_clone()
.unwrap() .unwrap()
.json(&RecordUpdate::new(vec![ .json(&RecordUpdate::new(vec![
RRSet::new( RRSet::new(
domain, domain.clone(),
"TXT", "TXT",
ChangeType::Replace { ChangeType::Replace {
records: vec![Record::new(content)], records: vec![Record::new(format!("\"{content}\""))],
comments: vec![Comment::new("ACME entry", "")], comments: vec![Comment::new("ACME entry", "")],
ttl: 0,
}, },
), ),
])) ]))
@ -83,49 +119,53 @@ impl DnsHandler for PdnsHandler {
{ {
Ok(_resp) if _resp.status() == StatusCode::NO_CONTENT => {}, Ok(_resp) if _resp.status() == StatusCode::NO_CONTENT => {},
Ok(resp) => { Ok(resp) => {
let err = match resp.json::<PdnsError>().await { let response_text = match resp.text().await {
Ok(error) => error.error, Ok(resp) => resp,
Err(error) => format!("{error}"), Err(error) => return Error::err(format!("Failed to get the errormessage: {error}")),
};
let err = match serde_json::from_slice::<PdnsError>(response_text.clone().as_bytes()) {
Ok(error) => {
trace!("The Server Error has {} suberrors", error.errors.len());
for suberror in error.errors {
trace!("SubError: {suberror}")
}
error.error
},
Err(error) => {
trace!("http Response: {response_text:?}");
format!("(Parsing error) {error}")
},
}; };
return Error::err(format!("Failed to set the record: {err}")); return Error::err(format!("Failed to set the record: {err}"));
}, },
Err(error) => return Error::err(format!("Failed to send the request to update the record: {error}")), Err(error) => return Error::err(format!("Failed to send the request to update the record: {error}")),
} }
Ok(Box::pin(PdnsToken { Ok(DnsToken::new_pdns(base_request.try_clone().unwrap().json(&RecordUpdate::new(vec![RRSet::new(domain, "TXT", ChangeType::Delete)]))))
builder: base_request.try_clone().unwrap().json(&0),
}))
} }
} }
/// Token that deletes the Record when its no longer needed /// Token that deletes the Record when its no longer needed
#[derive(Debug)] #[derive(Debug, new)]
pub(super) struct PdnsToken { pub struct PdnsToken {
builder: RequestBuilder, pub(crate) builder: RequestBuilder,
}
impl Clone for PdnsToken {
fn clone(&self) -> Self {
Self {
builder: self.builder.try_clone().unwrap(),
}
}
} }
impl PdnsToken { impl PdnsToken {
fn new(builder: RequestBuilder) -> Pin<Box<Self>> { pub async fn remove(&mut self) {
Box::pin(Self { match self.builder.try_clone().unwrap().send().await {
builder,
})
}
}
impl Drop for PdnsToken {
fn drop(&mut self) {
let handle = match Handle::try_current() {
Ok(handle) => handle,
Err(error) => {
error!("Failed to aquire the handle from tokio. Cleanup of the Token Failed: {error}");
return;
},
};
match handle.block_on(self.builder.try_clone().unwrap().send()) {
Ok(response) => { Ok(response) => {
if response.status() != StatusCode::NO_CONTENT { if response.status() != StatusCode::NO_CONTENT {
let status = response.status(); let status = response.status();
match handle.block_on(response.json::<PdnsError>()) { match response.json::<PdnsError>().await {
Ok(error) => error!("Failed to delete the Record({status}): {error}"), Ok(error) => error!("Failed to delete the Record({status}): {error}"),
Err(error) => error!("Failed to parse the Error Response({status}): {error}"), Err(error) => error!("Failed to parse the Error Response({status}): {error}"),
} }
@ -137,7 +177,6 @@ impl Drop for PdnsToken {
} }
unsafe impl Send for PdnsToken {} unsafe impl Send for PdnsToken {}
impl DnsToken for PdnsToken {}
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
#[derive(Deserialize)] #[derive(Deserialize)]
@ -183,6 +222,7 @@ enum ChangeType {
Replace { Replace {
records: Vec<Record>, records: Vec<Record>,
comments: Vec<Comment>, comments: Vec<Comment>,
ttl: u16,
}, },
Delete, Delete,
} }
@ -201,5 +241,5 @@ struct RRSet {
#[macro_rules_derive(DefDer)] #[macro_rules_derive(DefDer)]
#[derive(Serialize, new)] #[derive(Serialize, new)]
struct RecordUpdate { struct RecordUpdate {
rrset: Vec<RRSet>, rrsets: Vec<RRSet>,
} }

Datei anzeigen

@ -1,10 +1,6 @@
use std::{ use std::collections::HashSet;
collections::HashSet,
pin::Pin,
};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::types::traits::DnsToken;
pub mod config; pub mod config;
pub mod cryptography; pub mod cryptography;
@ -20,6 +16,3 @@ pub type VString = Vec<String>;
pub type SafeSet<T> = Mutex<HashSet<T>>; pub type SafeSet<T> = Mutex<HashSet<T>>;
pub type Result<T> = std::result::Result<T, structs::Error>; pub type Result<T> = std::result::Result<T, structs::Error>;
#[allow(type_alias_bounds)]
pub type BoxedResult<T: DnsToken> = std::result::Result<Pin<Box<T>>, structs::Error>;

Datei anzeigen

@ -16,7 +16,10 @@ use clap::{
}; };
use derive_new::new; use derive_new::new;
use macro_rules_attribute::macro_rules_derive; use macro_rules_attribute::macro_rules_derive;
use reqwest::Client; use reqwest::{
Client,
RequestBuilder,
};
use tokio::sync::MutexGuard; use tokio::sync::MutexGuard;
use crate::{ use crate::{
@ -28,7 +31,11 @@ use crate::{
Algorithm, Algorithm,
Strength, Strength,
}, },
dns::Manager, dns::{
Manager,
dnsupdate::DnsUpdateToken,
pdns::PdnsToken,
},
}, },
}; };
@ -86,11 +93,9 @@ impl<'a: 'b, 'b> ProcessorArgs<'a> {
attr_function!(pub challenge_dir => Option<PathBuf>); attr_function!(pub challenge_dir => Option<PathBuf>);
#[cfg(feature = "capabilities")] attr_function!(pub owner site => Option<String>);
attr_function!(pub owner site => String);
#[cfg(feature = "capabilities")] attr_function!(pub group site => Option<String>);
attr_function!(pub group site => String);
attr_function!(pub client => Client); attr_function!(pub client => Client);
@ -155,3 +160,33 @@ impl Error {
Err(Self::new(message.into())) Err(Self::new(message.into()))
} }
} }
#[macro_rules_derive(DefDer)]
pub enum DnsToken {
None,
Pdns(Box<PdnsToken>),
DnsUpdate(DnsUpdateToken),
}
impl DnsToken {
pub fn new_pdns(builder: RequestBuilder) -> Self {
Self::Pdns(Box::new(PdnsToken::new(builder)))
}
pub fn new_dns_update() -> Self {
Self::DnsUpdate(DnsUpdateToken {})
}
pub fn new_none() -> Self {
Self::None
}
pub async fn unset_token(&mut self) {
match self {
DnsToken::None => {},
DnsToken::Pdns(pdns_token) => pdns_token.remove().await,
DnsToken::DnsUpdate(dns_update_token) => dns_update_token.remove().await,
}
}
}

Datei anzeigen

@ -1,23 +1,25 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::consts::{
use crate::{
consts::{
BRAINPOOL_MIDDLE, BRAINPOOL_MIDDLE,
BRAINPOOL_STRONG, BRAINPOOL_STRONG,
BRAINPOOL_WEAK, BRAINPOOL_WEAK,
SECP_MIDDLE, SECP_MIDDLE,
SECP_STRONG, SECP_STRONG,
SECP_WEAK, SECP_WEAK,
}; },
use crate::{
types, types,
types::{ types::{
cryptography::{ cryptography::{
Algorithm, Algorithm,
Strength, Strength,
}, },
structs::San, structs::{
DnsToken,
San,
},
}, },
}; };
use log::*; use log::*;
@ -100,7 +102,7 @@ pub trait MatchX509 {
impl MatchX509 for X509 { impl MatchX509 for X509 {
fn days_left(&self, days: u32) -> bool { fn days_left(&self, days: u32) -> bool {
let future_date = Asn1Time::days_from_now(days).unwrap(); let future_date = Asn1Time::days_from_now(days).unwrap();
self.not_after().compare(&future_date).is_ok_and(|order| order.is_le()) self.not_after().compare(&future_date).is_ok_and(|order| order.is_gt())
} }
fn match_san<T: IntoIterator<Item = San>>(&self, names: T) -> bool { fn match_san<T: IntoIterator<Item = San>>(&self, names: T) -> bool {
@ -120,9 +122,6 @@ impl MatchX509 for X509 {
} }
} }
#[allow(drop_bounds)]
pub trait DnsToken: std::fmt::Debug + Send + Drop {}
pub trait DnsHandler: std::fmt::Debug + Send { pub trait DnsHandler: std::fmt::Debug + Send {
async fn set_record(&self, domain: String, content: String) -> types::BoxedResult<dyn DnsToken>; async fn set_record(&self, domain: String, content: String) -> types::Result<DnsToken>;
} }

Datei anzeigen

@ -109,10 +109,16 @@ pub fn string_to_cn(name: String) -> X509Name {
builder.build() builder.build()
} }
#[cfg(feature = "capabilities")]
pub fn check_permissions() -> bool { pub fn check_permissions() -> bool {
let caps = has_cap(None, CAPABILITY_SET, caps::Capability::CAP_CHOWN).unwrap_or(false) && #[allow(unused_mut)]
has_cap(None, CAPABILITY_SET, caps::Capability::CAP_FOWNER).unwrap_or(false); let mut perms: u8 = 0;
#[cfg(feature = "capabilities")]
{
if has_cap(None, CAPABILITY_SET, caps::Capability::CAP_CHOWN).unwrap_or(false) &&
has_cap(None, CAPABILITY_SET, caps::Capability::CAP_FOWNER).unwrap_or(false)
{
perms |= 1 << 0
}
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
for set in [ for set in [
CapSet::Ambient, CapSet::Ambient,
@ -131,53 +137,59 @@ pub fn check_permissions() -> bool {
} }
} }
} }
caps }
{
if unsafe { libc::geteuid() } == 0 {
perms |= 1 << 1;
}
}
perms > 0
} }
pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>) { fn clean_name(name: Option<String>) -> Option<CString> {
let uid = if username.contains('\0') { if let Some(name) = name {
error!("Invalid Username"); if name.contains('\0') {
error!("Invalid name: {name}");
return None;
}
match CString::new(name) {
Ok(cname) => Some(cname),
Err(error) => {
error!("Failed to convert the Username to an uid: {error}");
None None
},
}
} else { } else {
None
}
}
pub fn get_uid_gid(username: Option<String>, group: Option<String>) -> (Option<u32>, Option<u32>) {
let uid = if let Some(username) = clean_name(username) {
unsafe { unsafe {
match CString::new(username) { let passwd = libc::getpwnam(username.as_ptr());
Ok(cstr) => {
let passwd = libc::getpwnam(cstr.as_ptr());
if !passwd.is_null() { if !passwd.is_null() {
Some(passwd.read().pw_uid) Some(passwd.read().pw_uid)
} else { } else {
warn!("Failed to get user: User does not exist"); warn!("Failed to get user: User does not exist");
None None
} }
}, }
Err(error) => { } else {
error!("Failed to convert the Username to an uid: {error}");
None None
},
}
}
}; };
let gid = if group.contains('\0') { let gid = if let Some(group) = clean_name(group) {
error!("Invalid Group name");
None
} else {
unsafe { unsafe {
match CString::new(group) { let group = libc::getgrnam(group.as_ptr());
Ok(cstr) => { if !group.is_null() {
let passwd = libc::getgrnam(cstr.as_ptr()); Some(group.read().gr_gid)
if !passwd.is_null() {
Some(passwd.read().gr_gid)
} else { } else {
warn!("Failed to get user: User does not exist"); warn!("Failed to get group: Group does not exist");
None None
} }
}, }
Err(error) => { } else {
error!("Failed to convert the Group name to an uid: {error}");
None None
},
}
}
}; };
(uid, gid) (uid, gid)
} }