]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/dns.rs
ff04a5e3b49c37d3d775e84dec25228270cd7490
[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_router::{ApiMethod, Router, RpcEnvironment, Permission};
11 use proxmox_schema::api;
12 use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
13 use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
14
15 use 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 };
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
38 let mut result = json!({});
39
40 let mut nscount = 0;
41
42 let raw = file_get_contents(RESOLV_CONF_FN)?;
43
44 result["digest"] = Value::from(proxmox::tools::digest_to_hex(&sha::sha256(&raw)));
45
46 let data = String::from_utf8(raw)?;
47
48 lazy_static! {
49 static ref DOMAIN_REGEX: Regex = Regex::new(r"^\s*(?:search|domain)\s+(\S+)\s*").unwrap();
50 static ref SERVER_REGEX: Regex = Regex::new(
51 concat!(r"^\s*nameserver\s+(", IPRE!(), r")\s*")).unwrap();
52 }
53
54 let mut options = String::new();
55
56 for line in data.lines() {
57
58 if let Some(caps) = DOMAIN_REGEX.captures(&line) {
59 result["search"] = Value::from(&caps[1]);
60 } else if let Some(caps) = SERVER_REGEX.captures(&line) {
61 nscount += 1;
62 if nscount > 3 { continue };
63 let nameserver = &caps[1];
64 let id = format!("dns{}", nscount);
65 result[id] = Value::from(nameserver);
66 } else {
67 if !options.is_empty() { options.push('\n'); }
68 options.push_str(line);
69 }
70 }
71
72 if !options.is_empty() {
73 result["options"] = options.into();
74 }
75
76 Ok(result)
77 }
78
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,
89 optional: true,
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 },
103 delete: {
104 description: "List of properties to delete.",
105 type: Array,
106 optional: true,
107 items: {
108 type: DeletableProperty,
109 }
110 },
111 digest: {
112 optional: true,
113 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
114 },
115 },
116 },
117 access: {
118 permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_MODIFY, false),
119 }
120 )]
121 /// Update DNS settings
122 pub 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>,
129 ) -> Result<Value, Error> {
130
131 lazy_static! {
132 static ref MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
133 }
134
135 let _guard = MUTEX.lock();
136
137 let mut config = read_etc_resolv_conf()?;
138 let old_digest = config["digest"].as_str().unwrap();
139
140 if let Some(digest) = digest {
141 crate::tools::assert_if_modified(old_digest, &digest)?;
142 }
143
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 }
153 }
154
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(); }
159
160 let mut data = String::new();
161
162 if let Some(search) = config["search"].as_str() {
163 data.push_str(&format!("search {}\n", search));
164 }
165 for opt in &["dns1", "dns2", "dns3"] {
166 if let Some(server) = config[opt].as_str() {
167 data.push_str(&format!("nameserver {}\n", server));
168 }
169 }
170 if let Some(options) = config["options"].as_str() {
171 data.push_str(options);
172 }
173
174 replace_file(RESOLV_CONF_FN, data.as_bytes(), CreateOptions::new(), true)?;
175
176 Ok(Value::Null)
177 }
178
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 },
212 access: {
213 permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_AUDIT, false),
214 }
215 )]
216 /// Read DNS settings.
217 pub fn get_dns(
218 _param: Value,
219 _info: &ApiMethod,
220 _rpcenv: &mut dyn RpcEnvironment,
221 ) -> Result<Value, Error> {
222
223 read_etc_resolv_conf()
224 }
225
226 pub const ROUTER: Router = Router::new()
227 .get(&API_METHOD_GET_DNS)
228 .put(&API_METHOD_UPDATE_DNS);