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