#![deny(warnings, rust_2018_idioms)]
use std::env;
use std::ffi::CString;
use anyhow::{bail, Context, Result};
use git_testament::{git_testament, render_testament};
use libc;
use nix::unistd;
use tokio::runtime::Runtime;
use tracing::{self, debug, info};
use kapiti::{config, logging};
use kapiti::runner::Runner;
git_testament!(TESTAMENT);
fn print_syntax() {
println!(
"Kapiti DNS ({})
Syntax: {} </path/to/config.toml>
Environment variables:
LOG_LEVEL=error/warn/info/debug/trace/off",
render_testament!(TESTAMENT),
env::args().nth(0).unwrap()
);
}
fn main() -> Result<()> {
logging::init_logging();
if env::args().len() <= 1 {
print_syntax();
bail!("Missing required TOML config path argument");
}
let config_path = env::args().nth(1).unwrap();
if config_path.starts_with('-') {
// Probably a commandline argument like '-h'/'--help', avoid parsing as a path
print_syntax();
bail!("Unrecognized TOML config path argument: {}", config_path);
}
// Got past logging/args, print version message before getting into config parsing
info!("Kapiti DNS version {}", render_testament!(TESTAMENT));
let config = config::parse_config_file(&config_path)
.with_context(|| format!("Failed to parse TOML config: {}", config_path))?;
debug!("config: {:?}", config);
// Get downgrade user before passing ownership of config
let downgrade_user_orig = config.user.clone();
let downgrade_user = downgrade_user_orig.trim();
let mut runtime = Runtime::new()?;
// Needs to run async since it sets up the sockets internally
let mut runner = runtime.block_on(Runner::new(config_path, config))?;
// If currently root, downgrade to specified non-root user after Runner has set up any sockets
if !downgrade_user.is_empty() && unistd::geteuid().is_root() {
chuser(downgrade_user)
.with_context(|| format!("Failed to change to user: {}", downgrade_user))?;
info!("Changed to user: {}", downgrade_user);
}
// Manually start the service within a runtime.
runtime.block_on(runner.run())
}
/// Downgrades the user running the process to the provided username
/// This allows binding to port 53 as root, then running the daemon as a user
fn chuser(username: &str) -> Result<()> {
let mut result = std::ptr::null_mut::<libc::passwd>();
let ret = unsafe {
let mut pwd = std::mem::zeroed::<libc::passwd>();
let mut buf = vec![0; 4096];
libc::getpwnam_r(
CString::new(username.as_bytes())?.as_ptr(),
&mut pwd,
buf.as_mut_ptr(),
buf.len(),
&mut result,
)
};
if ret != 0 || result.is_null() {
bail!("User not found: {:?}", username);
}
let uid: u32;
let gid: u32;
unsafe {
uid = (*result).pw_uid;
gid = (*result).pw_gid;
}
if unsafe { libc::setgroups(1, &gid) } != 0 {
bail!("Unable to revoke supplementary groups");
}
unistd::setgid(unistd::Gid::from_raw(gid))
.with_context(|| format!("Unable to set process gid to {}/{}", username, gid))?;
unistd::setuid(unistd::Uid::from_raw(uid))
.with_context(|| format!("Unable to set process uid to {}/{}", username, uid))?;
Ok(())
}