~nickbp/originz

ref: 41895a73626b9e101ebd940b9de825c18ceb27f1 originz/src/main.rs -rw-r--r-- 4.0 KiB
41895a73Nick Parker Only build binary in docker build 11 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::env;
use std::ffi::CString;
use std::sync::Arc;
use std::thread;

use anyhow::{bail, Context, Result};
use async_lock::Barrier;
use git_testament::{git_testament, render_testament};
use libc;
use nix::unistd;
use signal_hook::{consts, iterator};
use tracing::{self, debug, info};

use kapiti::runner::Runner;
use kapiti::{config, logging};

git_testament!(TESTAMENT);

fn print_syntax() {
    println!(
        "Kapiti DNS ({})

Syntax: {} </path/to/config.toml>

Environment variables:
  CONFIG_PATH=/path/to/config.toml
  LOG_LEVEL=error/warn/info/debug/trace/off",
        render_testament!(TESTAMENT),
        env::args().nth(0).unwrap()
    );
}

fn main() -> Result<()> {
    logging::init_logging();

    // Try to get path via commandline, then fall back to envvar
    let config_path = match env::args().nth(1) {
        Some(arg) => {
            if arg.starts_with('-') {
                // Probably a commandline argument like '-h'/'--help', avoid parsing as a path
                print_syntax();
                bail!("Unrecognized TOML config path argument: {}", arg);
            }
            arg
        },
        None => {
            if let Ok(var) = env::var("CONFIG_PATH") {
                var
            } else {
                print_syntax();
                bail!("Missing required TOML config path argument");
            }
        }
    };

    // 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();

    // Needs to run async since it sets up the sockets internally
    let runner = smol::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);
    }

    // Run the service indefinitely, it will exit when the stop barrier returns.
    let barrier = Arc::new(Barrier::new(2));
    let barrier_copy = barrier.clone();
    let mut signals = iterator::Signals::new(&[consts::SIGINT, consts::SIGTERM])?;
    thread::spawn(move || {
        // Clean shutdown on Ctrl+C or TERM
        for sig in signals.forever() {
            info!("Received signal: {:?}", sig);
            smol::block_on(barrier_copy.wait());
        }
    });
    smol::block_on(runner.run(barrier))
}

/// 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(())
}