use std::{collections::HashMap, path::PathBuf, str::FromStr, time::Duration};
use anyhow::anyhow;
use ipnetwork::IpNetwork;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use openssl::{pkey::PKey, stack::Stack, x509::X509};
use crate::{
addresses::*,
authority::{find_members, RecordAuthority, ZTAuthority},
server::*,
traits::ToPointerSOA,
utils::*,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Launcher {
pub domain: Option<String>,
pub hosts: Option<PathBuf>,
pub secret: Option<PathBuf>,
pub token: Option<PathBuf>,
pub chain_cert: Option<PathBuf>,
pub tls_cert: Option<PathBuf>,
pub tls_key: Option<PathBuf>,
pub wildcard: bool,
pub log_level: Option<crate::log::LevelFilter>,
#[serde(skip_deserializing)]
pub network_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum ConfigFormat {
JSON,
YAML,
TOML,
}
impl FromStr for ConfigFormat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"json" | "JSON" => Ok(ConfigFormat::JSON),
"yaml" | "YAML" => Ok(ConfigFormat::YAML),
"toml" | "TOML" => Ok(ConfigFormat::TOML),
_ => Err(anyhow!(
"invalid format: allowed values: [json, yaml, toml]"
)),
}
}
}
impl Default for Launcher {
fn default() -> Self {
Launcher {
domain: None,
hosts: None,
secret: None,
token: None,
chain_cert: None,
tls_cert: None,
tls_key: None,
wildcard: false,
network_id: None,
log_level: None,
}
}
}
impl Launcher {
pub fn new_from_config(filename: &str, format: ConfigFormat) -> Result<Self, anyhow::Error> {
let res = std::fs::read_to_string(filename)?;
Self::parse_format(&res, format)
}
pub fn parse_format(s: &str, format: ConfigFormat) -> Result<Self, anyhow::Error> {
Ok(match format {
ConfigFormat::JSON => serde_json::from_str(&s)?,
ConfigFormat::YAML => serde_yaml::from_str(&s)?,
ConfigFormat::TOML => toml::from_str(&s)?,
})
}
pub fn parse(s: &str, network_id: String, format: ConfigFormat) -> Result<Self, anyhow::Error> {
let mut l: Launcher = Self::parse_format(s, format)?;
l.network_id = Some(network_id);
Ok(l)
}
pub async fn start(&self) -> Result<ZTAuthority, anyhow::Error> {
crate::utils::init_logger(
self.log_level
.clone()
.unwrap_or(crate::log::LevelFilter::Info)
.to_log(),
);
if self.network_id.is_none() {
return Err(anyhow!("network ID is invalid; cannot continue"));
}
let domain_name = domain_or_default(self.domain.as_deref())?;
let authtoken = authtoken_path(self.secret.as_deref());
let client = central_client(central_token(self.token.as_deref())?)?;
info!("Welcome to ZeroNS!");
let ips = get_listen_ips(&authtoken, &self.network_id.clone().unwrap()).await?;
if ips.len() > 0 {
update_central_dns(
domain_name.clone(),
ips.iter()
.map(|i| parse_ip_from_cidr(i.clone()).to_string())
.collect(),
client.clone(),
self.network_id.clone().unwrap(),
)
.await?;
let mut listen_ips = Vec::new();
let mut ipmap = HashMap::new();
let mut authority_map = HashMap::new();
for cidr in ips.clone() {
let listen_ip = parse_ip_from_cidr(cidr.clone());
listen_ips.push(listen_ip.clone());
let cidr = IpNetwork::from_str(&cidr.clone())?;
if !ipmap.contains_key(&listen_ip) {
ipmap.insert(listen_ip, cidr.network());
}
if !authority_map.contains_key(&cidr) {
tracing::debug!("{}", cidr.to_ptr_soa_name()?);
let ptr_authority =
RecordAuthority::new(cidr.to_ptr_soa_name()?, cidr.to_ptr_soa_name()?)
.await?;
authority_map.insert(cidr, ptr_authority);
}
}
let member_name = get_member_name(authtoken, domain_name.clone()).await?;
let network = client
.get_network_by_id(&self.network_id.clone().unwrap())
.await?;
if let Some(v6assign) = network.config.clone().unwrap().v6_assign_mode {
if v6assign._6plane.unwrap_or(false) {
warn!("6PLANE PTR records are not yet supported");
}
if v6assign.rfc4193.unwrap_or(false) {
let cidr = network.clone().rfc4193().unwrap();
if !authority_map.contains_key(&cidr) {
let ptr_authority =
RecordAuthority::new(cidr.to_ptr_soa_name()?, cidr.to_ptr_soa_name()?)
.await?;
authority_map.insert(cidr, ptr_authority);
}
}
}
let authority =
RecordAuthority::new(domain_name.clone().into(), member_name.clone()).await?;
let ztauthority = ZTAuthority {
client,
network_id: self.network_id.clone().unwrap(),
hosts: None, hosts_file: self.hosts.clone(),
reverse_authority_map: authority_map,
forward_authority: authority,
wildcard: self.wildcard,
update_interval: Duration::new(30, 0),
};
tokio::spawn(find_members(ztauthority.clone()));
let server = Server::new(ztauthority.to_owned());
for ip in listen_ips {
info!("Your IP for this network: {}", ip);
let tls_cert = if let Some(tls_cert) = self.tls_cert.clone() {
let pem = std::fs::read(tls_cert)?;
Some(X509::from_pem(&pem)?)
} else {
None
};
let chain = if let Some(chain_cert) = self.chain_cert.clone() {
let pem = std::fs::read(chain_cert)?;
let chain = X509::stack_from_pem(&pem)?;
let mut stack = Stack::new()?;
for cert in chain {
stack.push(cert)?;
}
Some(stack)
} else {
None
};
let key = if let Some(key_path) = self.tls_key.clone() {
let pem = std::fs::read(key_path)?;
Some(PKey::private_key_from_pem(&pem)?)
} else {
None
};
tokio::spawn(
server
.clone()
.listen(ip, Duration::new(1, 0), tls_cert, chain, key),
);
}
return Ok(ztauthority);
}
return Err(anyhow!(
"No listening IPs for your interface; assign one in ZeroTier Central."
));
}
}