]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/dns.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / src / api2 / node / dns.rs
CommitLineData
a2479cfa
WB
1use std::sync::{Arc, Mutex};
2
f7d4e4b5 3use anyhow::{Error};
a2479cfa
WB
4use lazy_static::lazy_static;
5use openssl::sha;
6use regex::Regex;
7use serde_json::{json, Value};
14627d67 8use ::serde::{Deserialize, Serialize};
b2b3485d 9
6ef1b649
WB
10use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
11use proxmox_schema::api;
25877d05
DM
12use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions};
13use pbs_api_types::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
7f66c29e 14
8cc3760e
DM
15use pbs_api_types::{
16 PROXMOX_CONFIG_DIGEST_SCHEMA, FIRST_DNS_SERVER_SCHEMA, SECOND_DNS_SERVER_SCHEMA,
17 THIRD_DNS_SERVER_SCHEMA, NODE_SCHEMA, SEARCH_DOMAIN_SCHEMA,
18 PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
19};
8f973f81 20
8f973f81
DM
21static RESOLV_CONF_FN: &str = "/etc/resolv.conf";
22
14627d67
DM
23#[api()]
24#[derive(Serialize, Deserialize)]
25#[allow(non_camel_case_types)]
26/// Deletable property name
27pub enum DeletableProperty {
28 /// Delete first nameserver entry
29 dns1,
30 /// Delete second nameserver entry
31 dns2,
32 /// Delete third nameserver entry
33 dns3,
34}
35
550e0d88 36pub fn read_etc_resolv_conf() -> Result<Value, Error> {
8f973f81
DM
37
38 let mut result = json!({});
39
40 let mut nscount = 0;
41
e18a6c9e 42 let raw = file_get_contents(RESOLV_CONF_FN)?;
8f973f81 43
25877d05 44 result["digest"] = Value::from(hex::encode(&sha::sha256(&raw)));
de6b0721
DM
45
46 let data = String::from_utf8(raw)?;
8f973f81
DM
47
48 lazy_static! {
af2fddea
DM
49 static ref DOMAIN_REGEX: Regex = Regex::new(r"^\s*(?:search|domain)\s+(\S+)\s*").unwrap();
50 static ref SERVER_REGEX: Regex = Regex::new(
8f973f81
DM
51 concat!(r"^\s*nameserver\s+(", IPRE!(), r")\s*")).unwrap();
52 }
53
14627d67
DM
54 let mut options = String::new();
55
de6b0721 56 for line in data.lines() {
8f973f81 57
9a37bd6c 58 if let Some(caps) = DOMAIN_REGEX.captures(line) {
46b79b9e 59 result["search"] = Value::from(&caps[1]);
9a37bd6c 60 } else if let Some(caps) = SERVER_REGEX.captures(line) {
8f973f81
DM
61 nscount += 1;
62 if nscount > 3 { continue };
46b79b9e 63 let nameserver = &caps[1];
8f973f81 64 let id = format!("dns{}", nscount);
46b79b9e 65 result[id] = Value::from(nameserver);
14627d67
DM
66 } else {
67 if !options.is_empty() { options.push('\n'); }
68 options.push_str(line);
8f973f81
DM
69 }
70 }
71
14627d67
DM
72 if !options.is_empty() {
73 result["options"] = options.into();
74 }
75
8f973f81
DM
76 Ok(result)
77}
b2b3485d 78
bd098a7f
DM
79#[api(
80 protected: true,
81 input: {
82 description: "Update DNS settings.",
83 properties: {
84 node: {
85 schema: NODE_SCHEMA,
86 },
87 search: {
88 schema: SEARCH_DOMAIN_SCHEMA,
14627d67 89 optional: true,
bd098a7f
DM
90 },
91 dns1: {
92 optional: true,
93 schema: FIRST_DNS_SERVER_SCHEMA,
94 },
95 dns2: {
96 optional: true,
97 schema: SECOND_DNS_SERVER_SCHEMA,
98 },
99 dns3: {
100 optional: true,
101 schema: THIRD_DNS_SERVER_SCHEMA,
102 },
14627d67
DM
103 delete: {
104 description: "List of properties to delete.",
105 type: Array,
106 optional: true,
107 items: {
108 type: DeletableProperty,
109 }
110 },
bd098a7f
DM
111 digest: {
112 optional: true,
113 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
114 },
115 },
116 },
4b40148c 117 access: {
74c08a57 118 permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_MODIFY, false),
4b40148c 119 }
bd098a7f
DM
120)]
121/// Update DNS settings
14627d67
DM
122pub fn update_dns(
123 search: Option<String>,
124 dns1: Option<String>,
125 dns2: Option<String>,
126 dns3: Option<String>,
127 delete: Option<Vec<DeletableProperty>>,
128 digest: Option<String>,
6049b71f 129) -> Result<Value, Error> {
46b79b9e 130
af2fddea 131 lazy_static! {
81b2a872 132 static ref MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
af2fddea
DM
133 }
134
9f49fe1d 135 let _guard = MUTEX.lock();
af2fddea 136
14627d67
DM
137 let mut config = read_etc_resolv_conf()?;
138 let old_digest = config["digest"].as_str().unwrap();
46b79b9e 139
14627d67
DM
140 if let Some(digest) = digest {
141 crate::tools::assert_if_modified(old_digest, &digest)?;
142 }
af2fddea 143
14627d67
DM
144 if let Some(delete) = delete {
145 for delete_prop in delete {
146 let config = config.as_object_mut().unwrap();
147 match delete_prop {
148 DeletableProperty::dns1 => { config.remove("dns1"); },
149 DeletableProperty::dns2 => { config.remove("dns2"); },
150 DeletableProperty::dns3 => { config.remove("dns3"); },
151 }
152 }
af2fddea
DM
153 }
154
14627d67
DM
155 if let Some(search) = search { config["search"] = search.into(); }
156 if let Some(dns1) = dns1 { config["dns1"] = dns1.into(); }
157 if let Some(dns2) = dns2 { config["dns2"] = dns2.into(); }
158 if let Some(dns3) = dns3 { config["dns3"] = dns3.into(); }
af2fddea 159
14627d67 160 let mut data = String::new();
46b79b9e 161
14627d67
DM
162 if let Some(search) = config["search"].as_str() {
163 data.push_str(&format!("search {}\n", search));
164 }
46b79b9e 165 for opt in &["dns1", "dns2", "dns3"] {
14627d67 166 if let Some(server) = config[opt].as_str() {
46b79b9e
DM
167 data.push_str(&format!("nameserver {}\n", server));
168 }
169 }
14627d67
DM
170 if let Some(options) = config["options"].as_str() {
171 data.push_str(options);
af2fddea
DM
172 }
173
e0a19d33 174 replace_file(RESOLV_CONF_FN, data.as_bytes(), CreateOptions::new(), true)?;
46b79b9e
DM
175
176 Ok(Value::Null)
177}
178
bd098a7f
DM
179#[api(
180 input: {
181 properties: {
182 node: {
183 schema: NODE_SCHEMA,
184 },
185 },
186 },
187 returns: {
188 description: "Returns DNS server IPs and sreach domain.",
189 type: Object,
190 properties: {
191 digest: {
192 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
193 },
194 search: {
195 optional: true,
196 schema: SEARCH_DOMAIN_SCHEMA,
197 },
198 dns1: {
199 optional: true,
200 schema: FIRST_DNS_SERVER_SCHEMA,
201 },
202 dns2: {
203 optional: true,
204 schema: SECOND_DNS_SERVER_SCHEMA,
205 },
206 dns3: {
207 optional: true,
208 schema: THIRD_DNS_SERVER_SCHEMA,
209 },
210 },
211 },
4b40148c 212 access: {
74c08a57 213 permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_AUDIT, false),
4b40148c 214 }
bd098a7f
DM
215)]
216/// Read DNS settings.
14627d67 217pub fn get_dns(
6049b71f
DM
218 _param: Value,
219 _info: &ApiMethod,
dd5495d6 220 _rpcenv: &mut dyn RpcEnvironment,
6049b71f 221) -> Result<Value, Error> {
b2b3485d 222
8f973f81 223 read_etc_resolv_conf()
b2b3485d
DM
224}
225
255f378a 226pub const ROUTER: Router = Router::new()
bd098a7f
DM
227 .get(&API_METHOD_GET_DNS)
228 .put(&API_METHOD_UPDATE_DNS);