use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
pub struct RndcConfFile {
pub keys: HashMap<String, KeyBlock>,
pub servers: HashMap<String, ServerBlock>,
pub options: OptionsBlock,
pub includes: Vec<PathBuf>,
}
impl RndcConfFile {
pub fn new() -> Self {
Self {
keys: HashMap::new(),
servers: HashMap::new(),
options: OptionsBlock::default(),
includes: Vec::new(),
}
}
pub fn get_default_key(&self) -> Option<&KeyBlock> {
let key_name = self.options.default_key.as_ref()?;
self.keys.get(key_name)
}
pub fn get_default_server(&self) -> Option<String> {
self.options.default_server.clone()
}
pub fn to_conf_file(&self) -> String {
let mut output = String::new();
for include_path in &self.includes {
output.push_str(&format!("include \"{}\";\n", include_path.display()));
}
for (name, key) in &self.keys {
output.push_str(&format!("\nkey \"{}\" {}\n", name, key.to_conf_block()));
}
for (addr, server) in &self.servers {
output.push_str(&format!("\nserver {} {}\n", addr, server.to_conf_block()));
}
if !self.options.is_empty() {
output.push_str(&format!("\noptions {}\n", self.options.to_conf_block()));
}
output
}
}
impl Default for RndcConfFile {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyBlock {
pub name: String,
pub algorithm: String,
pub secret: String,
}
impl KeyBlock {
pub fn new(name: String, algorithm: String, secret: String) -> Self {
Self {
name,
algorithm,
secret,
}
}
pub fn to_conf_block(&self) -> String {
format!(
"{{\n algorithm {};\n secret \"{}\";\n}};",
self.algorithm, self.secret
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ServerBlock {
pub address: ServerAddress,
pub key: Option<String>,
pub port: Option<u16>,
pub addresses: Option<Vec<IpAddr>>,
}
impl ServerBlock {
pub fn new(address: ServerAddress) -> Self {
Self {
address,
key: None,
port: None,
addresses: None,
}
}
pub fn to_conf_block(&self) -> String {
let mut parts = Vec::new();
if let Some(ref key) = self.key {
parts.push(format!(" key \"{}\";", key));
}
if let Some(port) = self.port {
parts.push(format!(" port {};", port));
}
if let Some(ref addrs) = self.addresses {
let addr_list = addrs
.iter()
.map(|ip| format!(" {};", ip))
.collect::<Vec<_>>()
.join("\n");
parts.push(format!(" addresses {{\n{}\n }};", addr_list));
}
if parts.is_empty() {
"{ };".to_string()
} else {
format!("{{\n{}\n}};", parts.join("\n"))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ServerAddress {
Hostname(String),
IpAddr(IpAddr),
}
impl ServerAddress {
pub fn parse(s: &str) -> Self {
match s.parse::<IpAddr>() {
Ok(addr) => ServerAddress::IpAddr(addr),
Err(_) => ServerAddress::Hostname(s.to_string()),
}
}
}
impl std::fmt::Display for ServerAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ServerAddress::Hostname(h) => write!(f, "{}", h),
ServerAddress::IpAddr(ip) => write!(f, "{}", ip),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct OptionsBlock {
pub default_server: Option<String>,
pub default_key: Option<String>,
pub default_port: Option<u16>,
}
impl OptionsBlock {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.default_server.is_none() && self.default_key.is_none() && self.default_port.is_none()
}
pub fn to_conf_block(&self) -> String {
let mut parts = Vec::new();
if let Some(ref server) = self.default_server {
parts.push(format!(" default-server {};", server));
}
if let Some(ref key) = self.default_key {
parts.push(format!(" default-key \"{}\";", key));
}
if let Some(port) = self.default_port {
parts.push(format!(" default-port {};", port));
}
if parts.is_empty() {
"{ };".to_string()
} else {
format!("{{\n{}\n}};", parts.join("\n"))
}
}
}
#[cfg(test)]
#[path = "rndc_conf_types_tests.rs"]
mod tests;