]> git.proxmox.com Git - proxmox-backup.git/commitdiff
api2/node/dns.rs: implement concurrent update protection
authorDietmar Maurer <dietmar@proxmox.com>
Fri, 25 Jan 2019 10:38:59 +0000 (11:38 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 25 Jan 2019 10:38:59 +0000 (11:38 +0100)
src/api2.rs
src/api2/node/dns.rs
src/tools.rs
src/tools/common_regex.rs

index 36ee87de315e79cdb5d573143849d689a8d825c2..b0c18ab091b55358a95ca6a53828aaf321f65f93 100644 (file)
@@ -19,6 +19,12 @@ use crate::tools::common_regex;
 lazy_static! {
     pub static ref IP_FORMAT: Arc<ApiStringFormat> = ApiStringFormat::Pattern(&common_regex::IP_REGEX).into();
 
+    pub static ref PVE_CONFIG_DIGEST_FORMAT: Arc<ApiStringFormat> =
+        ApiStringFormat::Pattern(&common_regex::SHA256_HEX_REGEX).into();
+
+    pub static ref PVE_CONFIG_DIGEST_SCHEMA: Arc<Schema> =
+        StringSchema::new("Prevent changes if current configuration file has different SHA256 digest. This can be used to prevent concurrent modifications.")
+        .format(PVE_CONFIG_DIGEST_FORMAT.clone()).into();
 
 }
 
index 5541ffff66174287f127e379dc07ed5f22cd8dda..eaac08f6b33a5e4e35ac8c4d6ebc8b278594fa2b 100644 (file)
@@ -9,8 +9,9 @@ use crate::api2::*;
 use lazy_static::lazy_static;
 
 use std::io::{BufRead, BufReader};
-use std::sync::Arc;
+use std::sync::{Arc, Mutex};
 use openssl::sha;
+use regex::Regex;
 
 use serde_json::{json, Value};
 
@@ -29,8 +30,8 @@ fn read_etc_resolv_conf() -> Result<Value, Error> {
     let data = String::from_utf8(raw)?;
 
     lazy_static! {
-        static ref DOMAIN_REGEX: regex::Regex = regex::Regex::new(r"^\s*(?:search|domain)\s+(\S+)\s*").unwrap();
-        static ref SERVER_REGEX: regex::Regex = regex::Regex::new(
+        static ref DOMAIN_REGEX: Regex = Regex::new(r"^\s*(?:search|domain)\s+(\S+)\s*").unwrap();
+        static ref SERVER_REGEX: Regex = Regex::new(
             concat!(r"^\s*nameserver\s+(", IPRE!(),  r")\s*")).unwrap();
     }
 
@@ -52,8 +53,23 @@ fn read_etc_resolv_conf() -> Result<Value, Error> {
 
 fn update_dns(param: Value, _info: &ApiMethod) -> Result<Value, Error> {
 
+    lazy_static! {
+        static ref MUTEX: Arc<Mutex<usize>> = Arc::new(Mutex::new(0));
+    }
+
+    let guard = MUTEX.lock();
+
     let search = tools::required_string_param(&param, "search")?;
 
+    let raw = tools::file_get_contents(RESOLV_CONF_FN)?;
+    let old_digest = tools::digest_to_hex(&sha::sha256(&raw));
+
+    if let Some(digest) = param["digest"].as_str() {
+        tools::assert_if_modified(&old_digest, &digest)?;
+    }
+
+    let old_data = String::from_utf8(raw)?;
+
     let mut data = format!("search {}\n", search);
 
     for opt in &["dns1", "dns2", "dns3"] {
@@ -62,6 +78,16 @@ fn update_dns(param: Value, _info: &ApiMethod) -> Result<Value, Error> {
         }
     }
 
+    // append other data
+    lazy_static! {
+        static ref SKIP_REGEX: Regex = Regex::new(r"^(search|domain|nameserver)\s+").unwrap();
+    }
+    for line in old_data.lines() {
+        if SKIP_REGEX.is_match(line) { continue; }
+        data.push_str(line);
+        data.push('\n');
+    }
+
     tools::file_set_contents(RESOLV_CONF_FN, data.as_bytes(), None)?;
 
     Ok(Value::Null)
@@ -98,6 +124,7 @@ pub fn router() -> Router {
                 ObjectSchema::new("Read DNS settings.")
             ).returns(
                 ObjectSchema::new("Returns DNS server IPs and sreach domain.")
+                    .required("digest", PVE_CONFIG_DIGEST_SCHEMA.clone())
                     .optional("search", SEARCH_DOMAIN_SCHEMA.clone())
                     .optional("dns1", FIRST_DNS_SERVER_SCHEMA.clone())
                     .optional("dns2", SECOND_DNS_SERVER_SCHEMA.clone())
@@ -112,6 +139,7 @@ pub fn router() -> Router {
                     .optional("dns1", FIRST_DNS_SERVER_SCHEMA.clone())
                     .optional("dns2", SECOND_DNS_SERVER_SCHEMA.clone())
                     .optional("dns3", THIRD_DNS_SERVER_SCHEMA.clone())
+                    .optional("digest", PVE_CONFIG_DIGEST_SCHEMA.clone())
              )
         );
 
index 39b3fada8ae156e0791a0732690a0fba06e6bd51..a706d84c0911b7a00f0af3766a456ebdd4d547bb 100644 (file)
@@ -412,3 +412,9 @@ pub fn digest_to_hex(digest: &[u8]) -> String {
     unsafe { String::from_utf8_unchecked(buf) }
 }
 
+pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
+    if digest1 != digest2 {
+       bail!("detected modified configuration - file changed by other user? Try again.");
+    }
+    Ok(())
+}
index 887e86bed758f5ca21c4d8985d58dab2effc2d17..322476be1608c1a3cf13e1c6530d7ca37fbd9c2c 100644 (file)
@@ -34,4 +34,6 @@ macro_rules! IPRE { () => (concat!(r"(?:", IPV4RE!(), "|", IPV6RE!(), ")")) }
 
 lazy_static! {
     pub static ref IP_REGEX: Regex = Regex::new(IPRE!()).unwrap();
+
+    pub static ref SHA256_HEX_REGEX: Regex = Regex::new("^[a-f0-9]{64}$").unwrap();
 }