]>
Commit | Line | Data |
---|---|---|
a2479cfa WB |
1 | use std::sync::{Arc, Mutex}; |
2 | ||
f7d4e4b5 | 3 | use anyhow::{Error}; |
a2479cfa WB |
4 | use lazy_static::lazy_static; |
5 | use openssl::sha; | |
6 | use regex::Regex; | |
7 | use serde_json::{json, Value}; | |
14627d67 | 8 | use ::serde::{Deserialize, Serialize}; |
b2b3485d | 9 | |
6ef1b649 WB |
10 | use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission}; |
11 | use proxmox_schema::api; | |
25877d05 DM |
12 | use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions}; |
13 | use pbs_api_types::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}; | |
7f66c29e | 14 | |
8cc3760e DM |
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 | }; | |
8f973f81 | 20 | |
8f973f81 DM |
21 | static 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 | |
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 | ||
550e0d88 | 36 | pub 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 |
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>, | |
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 | 217 | pub 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 | 226 | pub const ROUTER: Router = Router::new() |
bd098a7f DM |
227 | .get(&API_METHOD_GET_DNS) |
228 | .put(&API_METHOD_UPDATE_DNS); |