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