summaryrefslogtreecommitdiff
path: root/src/daemon_dbus.rs
blob: 5ed4d9475d844377a7aa1f93f14ef0e78479c6be (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use base64::prelude::*;
use dbus::{
    channel::{MatchingReceiver, Token},
    message::MatchRule,
    nonblock::SyncConnection,
};
use dbus_crossroads::{Context, Crossroads, MethodErr};
use defguard_wireguard_rs::{key::Key, net::IpAddrMask, WireguardInterfaceApi};
use log::{debug, error, info, warn};
use rand::Rng;
use std::{
    collections::HashMap, marker::PhantomData, ops::DerefMut, path::PathBuf, str::FromStr,
    sync::Arc,
};
use tokio::{net::TcpListener, sync::RwLock, task};

use crate::{daemon::*, daemon_config::*, daemon_network::*};

pub async fn start_dbus(
    state: Arc<RwLock<State>>,
    config_path: PathBuf,
) -> Result<(Arc<SyncConnection>, Token), DaemonError> {
    let mut cr = Crossroads::new();
    let if_token = cr.register("de.a.maesch", move |b| {
        b.signal::<(String, String), _>("Proposal", ("network", "peer_data"));
        b.method_with_cr_async(
            "AddNetwork",
            ("name", "key", "ip", "listen_port", "maesch_port"),
            ("success",),
            move |ctx, _, args: (String, String, String, u16, u16)| {
                debug!("Received AddNetwork");
                handle_add_network(ctx, state.clone(), config_path.clone(), args)
            },
        );
    });
    cr.insert("/de/a/maesch", &[if_token], ());

    // drive dbus interface
    let (res, c) = dbus_tokio::connection::new_system_sync()?;
    cr.set_async_support(Some((
        c.clone(),
        Box::new(|x| {
            tokio::spawn(x);
        }),
    )));
    let _ = tokio::spawn(print_error(async {
        res.await;
        Result::<!, &'static str>::Err("lost connection to dbus!")
    }));
    let receive_token = c.start_receive(
        MatchRule::new_method_call(),
        Box::new(move |msg, conn| {
            cr.handle_message(msg, conn).unwrap();
            true
        }),
    );
    c.request_name("de.a.maesch", false, true, false).await?;

    Ok((c, receive_token))
}

// TODO also take peers
pub async fn handle_add_network(
    mut ctx: Context,
    state: Arc<RwLock<State>>,
    config_path: PathBuf,
    (name, may_key, may_ip, may_lp, may_mp): (String, String, String, u16, u16),
) -> PhantomData<(bool,)> {
    // NOTE: this is kinda stupid: we convert to a string later anyways, as thats what
    // defguard_wg takes...
    let key = Key::new(match may_key.as_str() {
        "" => rand::thread_rng().gen(),
        _ => match BASE64_STANDARD.decode(may_key) {
            Ok(v) if v.len() == 32 => v.try_into().unwrap(),
            _ => {
                warn!("AddNetwork with bad key");
                return ctx.reply(Err(MethodErr::invalid_arg("bad key")));
            }
        },
    });

    // we store the ip as the original string, but should validate it regardless
    let (ip, ip_string) = match may_ip.as_str() {
        "" => todo!(),
        _ => match IpAddrMask::from_str(&may_ip) {
            Ok(ip_mask) => (ip_mask.ip, may_ip),
            Err(_) => {
                warn!("AddNetwork with bad ip");
                return ctx.reply(Err(MethodErr::invalid_arg("invalid ip")));
            }
        },
    };

    let lp = if may_lp == 0 { 25565 } else { may_lp };
    let mp = if may_mp == 0 { 51820 } else { may_mp };

    let (wg_api, hostnames) = match add_network(
        name.clone(),
        key.to_string(),
        ip_string,
        lp,
        &HashMap::new(),
    )
    .await
    {
        Ok(v) => v,
        Err(e) => {
            warn!("AddNetwork couldn't add network: {e}");
            return ctx.reply(Err(MethodErr::failed(&e)));
        }
    };

    let listener = match TcpListener::bind((ip, mp)).await {
        Ok(l) => l,
        Err(e) => {
            let _ = wg_api.remove_interface();
            warn!("AddNetwork couldn't start listener: {e}");
            return ctx.reply(Err(MethodErr::failed(&e)));
        }
    };
    let h = task::spawn(print_error(run_network(
        state.clone(),
        listener,
        name.clone(),
    )));

    let mut state_rw_guard = state.write().await;
    let state_rw = state_rw_guard.deref_mut();
    state_rw.nw_handles.insert(name.clone(), (wg_api, h));

    // NOTE this _is_ thread-safe, as we hold an exclusive write handle to the state.
    match write_config(&state_rw.conf, &config_path) {
        Ok(_) => info!("Synced config"),
        Err(e) => error!("Couldn't sync config: {e}"),
    }
    // similarly, this is still racy w.r.t. other processes running on the system, but at least
    // no other thread from this program should be able to concurrently call this
    match sync_hostnames(&hostnames) {
        Ok(_) => (),
        Err(e) => error!("Failed to sync hostnames to disk: {e}"),
    };

    ctx.reply(Ok((true,)))
}