From 14627d671a6af2a014caea8321a241758988bc1c Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sun, 26 Apr 2020 08:23:23 +0200 Subject: [PATCH] src/bin/proxmox-backup-manager.rs: add dns sub command Also improved the DNS api, added a --delete option. --- src/api2/node/dns.rs | 93 ++++++++++++++++++++++--------- src/bin/proxmox-backup-manager.rs | 52 +++++++++++++++++ 2 files changed, 120 insertions(+), 25 deletions(-) diff --git a/src/api2/node/dns.rs b/src/api2/node/dns.rs index b87f6a53..fe1e0fce 100644 --- a/src/api2/node/dns.rs +++ b/src/api2/node/dns.rs @@ -5,6 +5,7 @@ use lazy_static::lazy_static; use openssl::sha; use regex::Regex; use serde_json::{json, Value}; +use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; @@ -15,6 +16,19 @@ use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; static RESOLV_CONF_FN: &str = "/etc/resolv.conf"; +#[api()] +#[derive(Serialize, Deserialize)] +#[allow(non_camel_case_types)] +/// Deletable property name +pub enum DeletableProperty { + /// Delete first nameserver entry + dns1, + /// Delete second nameserver entry + dns2, + /// Delete third nameserver entry + dns3, +} + pub fn read_etc_resolv_conf() -> Result { let mut result = json!({}); @@ -33,6 +47,8 @@ pub fn read_etc_resolv_conf() -> Result { concat!(r"^\s*nameserver\s+(", IPRE!(), r")\s*")).unwrap(); } + let mut options = String::new(); + for line in data.lines() { if let Some(caps) = DOMAIN_REGEX.captures(&line) { @@ -43,9 +59,16 @@ pub fn read_etc_resolv_conf() -> Result { let nameserver = &caps[1]; let id = format!("dns{}", nscount); result[id] = Value::from(nameserver); + } else { + if !options.is_empty() { options.push('\n'); } + options.push_str(line); } } + if !options.is_empty() { + result["options"] = options.into(); + } + Ok(result) } @@ -59,6 +82,7 @@ pub fn read_etc_resolv_conf() -> Result { }, search: { schema: SEARCH_DOMAIN_SCHEMA, + optional: true, }, dns1: { optional: true, @@ -72,6 +96,14 @@ pub fn read_etc_resolv_conf() -> Result { optional: true, schema: THIRD_DNS_SERVER_SCHEMA, }, + delete: { + description: "List of properties to delete.", + type: Array, + optional: true, + items: { + type: DeletableProperty, + } + }, digest: { optional: true, schema: PROXMOX_CONFIG_DIGEST_SCHEMA, @@ -83,10 +115,13 @@ pub fn read_etc_resolv_conf() -> Result { } )] /// Update DNS settings -fn update_dns( - param: Value, - _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, +pub fn update_dns( + search: Option, + dns1: Option, + dns2: Option, + dns3: Option, + delete: Option>, + digest: Option, ) -> Result { lazy_static! { @@ -95,33 +130,41 @@ fn update_dns( let _guard = MUTEX.lock(); - let search = crate::tools::required_string_param(¶m, "search")?; + let mut config = read_etc_resolv_conf()?; + let old_digest = config["digest"].as_str().unwrap(); - let raw = file_get_contents(RESOLV_CONF_FN)?; - let old_digest = proxmox::tools::digest_to_hex(&sha::sha256(&raw)); - - if let Some(digest) = param["digest"].as_str() { - crate::tools::assert_if_modified(&old_digest, &digest)?; + if let Some(digest) = digest { + crate::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"] { - if let Some(server) = param[opt].as_str() { - data.push_str(&format!("nameserver {}\n", server)); + if let Some(delete) = delete { + for delete_prop in delete { + let config = config.as_object_mut().unwrap(); + match delete_prop { + DeletableProperty::dns1 => { config.remove("dns1"); }, + DeletableProperty::dns2 => { config.remove("dns2"); }, + DeletableProperty::dns3 => { config.remove("dns3"); }, + } } } - // append other data - lazy_static! { - static ref SKIP_REGEX: Regex = Regex::new(r"^(search|domain|nameserver)\s+").unwrap(); + if let Some(search) = search { config["search"] = search.into(); } + if let Some(dns1) = dns1 { config["dns1"] = dns1.into(); } + if let Some(dns2) = dns2 { config["dns2"] = dns2.into(); } + if let Some(dns3) = dns3 { config["dns3"] = dns3.into(); } + + let mut data = String::new(); + + if let Some(search) = config["search"].as_str() { + data.push_str(&format!("search {}\n", search)); } - for line in old_data.lines() { - if SKIP_REGEX.is_match(line) { continue; } - data.push_str(line); - data.push('\n'); + for opt in &["dns1", "dns2", "dns3"] { + if let Some(server) = config[opt].as_str() { + data.push_str(&format!("nameserver {}\n", server)); + } + } + if let Some(options) = config["options"].as_str() { + data.push_str(options); } replace_file(RESOLV_CONF_FN, data.as_bytes(), CreateOptions::new())?; @@ -167,7 +210,7 @@ fn update_dns( } )] /// Read DNS settings. -fn get_dns( +pub fn get_dns( _param: Value, _info: &ApiMethod, _rpcenv: &mut dyn RpcEnvironment, diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index a19eec8b..f12d9632 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -365,6 +365,57 @@ fn network_commands() -> CommandLineInterface { cmd_def.into() } +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + } + } +)] +/// Read DNS settings +fn get_dns(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result { + + let output_format = get_output_format(¶m); + + param["node"] = "localhost".into(); + + let info = &api2::node::dns::API_METHOD_GET_DNS; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + + let options = default_table_format_options() + .column(ColumnConfig::new("search")) + .column(ColumnConfig::new("dns1")) + .column(ColumnConfig::new("dns2")) + .column(ColumnConfig::new("dns3")); + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(Value::Null) +} + +fn dns_commands() -> CommandLineInterface { + + let cmd_def = CliCommandMap::new() + .insert( + "get", + CliCommand::new(&API_METHOD_GET_DNS) + ) + .insert( + "set", + CliCommand::new(&api2::node::dns::API_METHOD_UPDATE_DNS) + .fixed_param("node", String::from("localhost")) + ); + + cmd_def.into() +} + #[api( input: { properties: { @@ -766,6 +817,7 @@ fn main() { let cmd_def = CliCommandMap::new() .insert("acl", acl_commands()) .insert("datastore", datastore_commands()) + .insert("dns", dns_commands()) .insert("network", network_commands()) .insert("user", user_commands()) .insert("remote", remote_commands())