ZeroTierOne/service/src/main.rs
2022-11-28 12:36:35 -05:00

256 lines
11 KiB
Rust

// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
pub mod cli;
pub mod cmdline_help;
pub mod localconfig;
pub mod utils;
pub mod vnic;
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use clap::error::{ContextKind, ContextValue};
#[allow(unused_imports)]
use clap::{Arg, ArgMatches, Command};
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
use zerotier_utils::exitcode;
use zerotier_vl1_service::datadir::DataDir;
use zerotier_vl1_service::VL1Service;
use crate::localconfig::Config;
pub fn print_help() {
let h = crate::cmdline_help::make_cmdline_help();
let _ = std::io::stdout().write_all(h.as_bytes());
}
#[cfg(target_os = "macos")]
pub fn platform_default_home_path() -> String {
"/Library/Application Support/ZeroTier".into()
}
#[cfg(target_os = "linux")]
pub fn platform_default_home_path() -> String {
"/var/lib/zerotier".into()
}
pub struct Flags {
pub json_output: bool,
pub base_path: String,
pub auth_token_path_override: Option<String>,
pub auth_token_override: Option<String>,
}
fn open_datadir(flags: &Flags) -> Arc<DataDir<Config>> {
let datadir = DataDir::open(flags.base_path.as_str());
if datadir.is_ok() {
return Arc::new(datadir.unwrap());
}
eprintln!(
"FATAL: unable to open data directory {}: {}",
flags.base_path,
datadir.err().unwrap().to_string()
);
std::process::exit(exitcode::ERR_IOERR);
}
fn main() {
let global_args = Box::new({
Command::new("zerotier")
.arg(Arg::new("json").short('j'))
.arg(Arg::new("path").short('p').takes_value(true))
.arg(Arg::new("token_path").short('t').takes_value(true))
.arg(Arg::new("token").short('T').takes_value(true))
.subcommand_required(true)
.subcommand(Command::new("help"))
.subcommand(Command::new("version"))
.subcommand(Command::new("status"))
.subcommand(
Command::new("set")
.subcommand(Command::new("port").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand(Command::new("secondaryport").arg(Arg::new("port#").index(1).validator(utils::is_valid_port)))
.subcommand(
Command::new("blacklist")
.subcommand(
Command::new("cidr")
.arg(Arg::new("ip_bits").index(1))
.arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool)),
)
.subcommand(
Command::new("if")
.arg(Arg::new("prefix").index(1))
.arg(Arg::new("boolean").index(2).validator(utils::is_valid_bool)),
),
)
.subcommand(Command::new("portmap").arg(Arg::new("boolean").index(1).validator(utils::is_valid_bool))),
)
.subcommand(
Command::new("peer")
.subcommand(Command::new("show").arg(Arg::new("address").index(1).required(true)))
.subcommand(Command::new("list"))
.subcommand(Command::new("listroots"))
.subcommand(Command::new("try")),
)
.subcommand(
Command::new("network")
.subcommand(Command::new("show").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("list"))
.subcommand(
Command::new("set")
.arg(Arg::new("nwid").index(1).required(true))
.arg(Arg::new("setting").index(2).required(false))
.arg(Arg::new("value").index(3).required(false)),
),
)
.subcommand(Command::new("join").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("leave").arg(Arg::new("nwid").index(1).required(true)))
.subcommand(Command::new("service"))
.subcommand(
Command::new("identity")
.subcommand(Command::new("new"))
.subcommand(Command::new("getpublic").arg(Arg::new("identity").index(1).required(true)))
.subcommand(Command::new("fingerprint").arg(Arg::new("identity").index(1).required(true)))
.subcommand(Command::new("validate").arg(Arg::new("identity").index(1).required(true)))
.subcommand(
Command::new("sign")
.arg(Arg::new("identity").index(1).required(true))
.arg(Arg::new("path").index(2).required(true)),
)
.subcommand(
Command::new("verify")
.arg(Arg::new("identity").index(1).required(true))
.arg(Arg::new("path").index(2).required(true))
.arg(Arg::new("signature").index(3).required(true)),
),
)
.subcommand(
Command::new("rootset")
.subcommand(Command::new("add").arg(Arg::new("path").index(1).required(true)))
.subcommand(Command::new("remove").arg(Arg::new("name").index(1).required(true)))
.subcommand(Command::new("list"))
.subcommand(
Command::new("sign")
.arg(Arg::new("path").index(1).required(true))
.arg(Arg::new("secret").index(2).required(true)),
)
.subcommand(Command::new("verify").arg(Arg::new("path").index(1).required(true)))
.subcommand(Command::new("marshal").arg(Arg::new("path").index(1).required(true)))
.subcommand(Command::new("restoredefault")),
)
.override_help(crate::cmdline_help::make_cmdline_help().as_str())
.override_usage("")
.disable_version_flag(true)
.disable_help_subcommand(false)
.disable_help_flag(true)
.try_get_matches_from(std::env::args())
.unwrap_or_else(|e| {
if e.kind() == clap::ErrorKind::DisplayHelp || e.kind() == clap::ErrorKind::MissingSubcommand {
print_help();
std::process::exit(exitcode::OK);
} else {
let mut invalid = String::default();
let mut suggested = String::default();
for c in e.context() {
match c {
(ContextKind::SuggestedSubcommand | ContextKind::SuggestedArg, ContextValue::String(name)) => {
suggested = name.clone();
}
(ContextKind::InvalidArg | ContextKind::InvalidSubcommand, ContextValue::String(name)) => {
invalid = name.clone();
}
_ => {}
}
}
if invalid.is_empty() {
eprintln!("Invalid command line. Use 'help' for help.");
} else {
if suggested.is_empty() {
eprintln!("Unrecognized option '{}'. Use 'help' for help.", invalid);
} else {
eprintln!(
"Unrecognized option '{}', did you mean {}? Use 'help' for help.",
invalid, suggested
);
}
}
std::process::exit(exitcode::ERR_USAGE);
}
})
});
let flags = Flags {
json_output: global_args.is_present("json"),
base_path: global_args
.value_of("path")
.map_or_else(platform_default_home_path, |p| p.to_string()),
auth_token_path_override: global_args.value_of("token_path").map(|p| p.to_string()),
auth_token_override: global_args.value_of("token").map(|t| t.to_string()),
};
#[allow(unused)]
let exit_code = match global_args.subcommand() {
Some(("help", _)) => {
print_help();
exitcode::OK
}
Some(("version", _)) => {
println!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
exitcode::OK
}
Some(("status", _)) => todo!(),
Some(("set", cmd_args)) => todo!(),
Some(("peer", cmd_args)) => todo!(),
Some(("network", cmd_args)) => todo!(),
Some(("join", cmd_args)) => todo!(),
Some(("leave", cmd_args)) => todo!(),
Some(("service", _)) => {
drop(global_args); // free unnecessary heap before starting service as we're done with CLI args
if let Ok(_tokio_runtime) = zerotier_utils::tokio::runtime::Builder::new_multi_thread().enable_all().build() {
let test_inner = Arc::new(zerotier_network_hypervisor::vl1::DummyInnerProtocol::default());
let datadir = open_datadir(&flags);
let svc = VL1Service::new(
datadir,
test_inner.clone(),
test_inner,
zerotier_vl1_service::VL1Settings::default(),
);
if svc.is_ok() {
let svc = svc.unwrap();
svc.node().init_default_roots();
// Wait for kill signal on Unix-like platforms.
#[cfg(unix)]
{
let term = Arc::new(AtomicBool::new(false));
let _ = signal_hook::flag::register(libc::SIGINT, term.clone());
let _ = signal_hook::flag::register(libc::SIGTERM, term.clone());
let _ = signal_hook::flag::register(libc::SIGQUIT, term.clone());
while !term.load(Ordering::Relaxed) {
std::thread::sleep(Duration::from_secs(1));
}
}
println!("Terminate signal received, shutting down...");
exitcode::OK
} else {
eprintln!("FATAL: error launching service: {}", svc.err().unwrap().to_string());
exitcode::ERR_IOERR
}
} else {
eprintln!("FATAL: error launching service: can't start async runtime");
exitcode::ERR_IOERR
}
}
Some(("identity", cmd_args)) => todo!(),
Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args),
_ => {
eprintln!("Invalid command line. Use 'help' for help.");
exitcode::ERR_USAGE
}
};
std::process::exit(exit_code);
}