summaryrefslogtreecommitdiff
path: root/src/daemon_config.rs
blob: 246a736c7542369008c48abd9a22f29529a82413 (plain)
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
use atomic_write_file::AtomicWriteFile;
use defguard_wireguard_rs::{key::Key, net::IpAddrMask};
use log::info;
use serde::{Deserialize, Serialize};
use std::{
    collections::{BTreeSet, HashMap},
    fs::File,
    io::{ErrorKind, Read, Write},
    net::SocketAddr,
    path::PathBuf,
    time::SystemTime,
};
use xdg::BaseDirectories;

use crate::daemon::DaemonError;

#[derive(Serialize, Deserialize, Clone)]
pub enum Endpoint {
    Ip(SocketAddr),
    Domain(String, u16),
}

// subset of defguard_wireguard_rs::host::Peer, with hostname added
#[derive(Serialize, Deserialize)]
pub struct PeerConfig {
    pub psk: Option<Key>,
    pub ips: Vec<(IpAddrMask, Option<String>)>,
    // if false: the hostnames are kept around for sharing, but we personally do not use them
    pub use_hostnames: bool,
    pub endpoint: Option<Endpoint>,

    pub last_changed: SystemTime,
    pub known_to: Vec<usize>,

    pub mäsch_endpoint: SocketAddr,
}

fn default_wg_port() -> u16 {
    51820
}

#[derive(Serialize, Deserialize)]
pub struct Network {
    pub privkey: String,
    // this really should be a different type, but this is what defguard takes...
    pub address: String,
    #[serde(default = "default_wg_port")]
    pub listen_port: u16,
    pub peers: HashMap<Key, PeerConfig>,

    pub mäsch_port: u16,
}

#[derive(Serialize, Deserialize, Default)]
pub struct Config {
    pub networks: HashMap<String, Network>,
}

pub fn load_config() -> Result<(PathBuf, Config), DaemonError> {
    let config_path = BaseDirectories::with_prefix("mäsch")?.place_state_file("daemon.json")?;
    let config: Config = match File::open(&config_path) {
        Ok(f) => serde_json::from_reader(f)?,
        Err(e) => match e.kind() {
            ErrorKind::NotFound => Config::default(),
            _ => Err(e)?,
        },
    };
    info!("read config");

    Ok((config_path, config))
}

pub fn write_config(conf: &Config, path: &PathBuf) -> Result<(), std::io::Error> {
    let mut f = AtomicWriteFile::open(path)?;
    serde_json::to_writer(&mut f, conf)?;
    f.commit()?;

    Ok(())
}

pub fn sync_hostnames(hostnames: &BTreeSet<(String, String)>) -> Result<(), std::io::Error> {
    let mut hosts_str = match File::open("/etc/hosts") {
        Ok(mut f) => {
            let mut r = String::new();
            f.read_to_string(&mut r)?;
            r
        }
        Err(e) => match e.kind() {
            ErrorKind::NotFound => "".to_owned(),
            _ => return Err(e),
        },
    };

    let seen_hostnames: BTreeSet<String> = hosts_str
        .lines()
        .map(|l| {
            l.split_whitespace()
                .take_while(|dom| dom.chars().next().unwrap() != '#')
                .skip(1)
        })
        .flatten()
        .map(|dom| dom.to_owned())
        .collect();

    for (ip, dom) in hostnames {
        if !seen_hostnames.contains(dom.as_str()) {
            hosts_str.push_str(ip);
            hosts_str.push(' ');
            hosts_str.push_str(&format!("{dom}"));
            hosts_str.push('\n');
        }
    }

    info!("Syncing host file");

    let mut f = AtomicWriteFile::open("/etc/hosts")?;
    f.write(hosts_str.as_bytes())?;
    f.commit()?;

    Ok(())
}