-use failure::*;
-
-
-use crate::tools;
-use crate::api2::*;
-//use crate::api_schema::schema::*;
-//use crate::api_schema::router::*;
+use std::sync::{Arc, Mutex};
+use anyhow::{Error};
use lazy_static::lazy_static;
-
-use std::sync::{Arc, Mutex};
use openssl::sha;
use regex::Regex;
-
use serde_json::{json, Value};
+use ::serde::{Deserialize, Serialize};
+
+use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
+use proxmox_schema::api;
+use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
+use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
+
+use pbs_api_types::{
+ PROXMOX_CONFIG_DIGEST_SCHEMA, FIRST_DNS_SERVER_SCHEMA, SECOND_DNS_SERVER_SCHEMA,
+ THIRD_DNS_SERVER_SCHEMA, NODE_SCHEMA, SEARCH_DOMAIN_SCHEMA,
+ PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
+};
static RESOLV_CONF_FN: &str = "/etc/resolv.conf";
-fn read_etc_resolv_conf() -> Result<Value, Error> {
+#[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<Value, Error> {
let mut result = json!({});
let mut nscount = 0;
- let raw = tools::file_get_contents(RESOLV_CONF_FN)?;
+ let raw = file_get_contents(RESOLV_CONF_FN)?;
- result["digest"] = Value::from(tools::digest_to_hex(&sha::sha256(&raw)));
+ result["digest"] = Value::from(proxmox::tools::digest_to_hex(&sha::sha256(&raw)));
let data = String::from_utf8(raw)?;
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) {
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)
}
-fn update_dns(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+#[api(
+ protected: true,
+ input: {
+ description: "Update DNS settings.",
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ search: {
+ schema: SEARCH_DOMAIN_SCHEMA,
+ optional: true,
+ },
+ dns1: {
+ optional: true,
+ schema: FIRST_DNS_SERVER_SCHEMA,
+ },
+ dns2: {
+ optional: true,
+ schema: SECOND_DNS_SERVER_SCHEMA,
+ },
+ dns3: {
+ 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,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_MODIFY, false),
+ }
+)]
+/// Update DNS settings
+pub fn update_dns(
+ search: Option<String>,
+ dns1: Option<String>,
+ dns2: Option<String>,
+ dns3: Option<String>,
+ delete: Option<Vec<DeletableProperty>>,
+ digest: Option<String>,
) -> Result<Value, Error> {
lazy_static! {
- static ref MUTEX: Arc<Mutex<usize>> = Arc::new(Mutex::new(0));
+ static ref MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
let _guard = MUTEX.lock();
- let search = tools::required_string_param(¶m, "search")?;
+ let mut config = read_etc_resolv_conf()?;
+ let old_digest = config["digest"].as_str().unwrap();
- let raw = tools::file_get_contents(RESOLV_CONF_FN)?;
- let old_digest = tools::digest_to_hex(&sha::sha256(&raw));
+ if let Some(digest) = digest {
+ crate::tools::assert_if_modified(old_digest, &digest)?;
+ }
- if let Some(digest) = param["digest"].as_str() {
- tools::assert_if_modified(&old_digest, &digest)?;
+ 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"); },
+ }
+ }
}
- let old_data = String::from_utf8(raw)?;
+ 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 = format!("search {}\n", search);
+ let mut data = String::new();
+ if let Some(search) = config["search"].as_str() {
+ data.push_str(&format!("search {}\n", search));
+ }
for opt in &["dns1", "dns2", "dns3"] {
- if let Some(server) = param[opt].as_str() {
+ if let Some(server) = config[opt].as_str() {
data.push_str(&format!("nameserver {}\n", server));
}
}
-
- // 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');
+ if let Some(options) = config["options"].as_str() {
+ data.push_str(options);
}
- tools::file_set_contents(RESOLV_CONF_FN, data.as_bytes(), None)?;
+ replace_file(RESOLV_CONF_FN, data.as_bytes(), CreateOptions::new())?;
Ok(Value::Null)
}
-fn get_dns(
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ description: "Returns DNS server IPs and sreach domain.",
+ type: Object,
+ properties: {
+ digest: {
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ search: {
+ optional: true,
+ schema: SEARCH_DOMAIN_SCHEMA,
+ },
+ dns1: {
+ optional: true,
+ schema: FIRST_DNS_SERVER_SCHEMA,
+ },
+ dns2: {
+ optional: true,
+ schema: SECOND_DNS_SERVER_SCHEMA,
+ },
+ dns3: {
+ optional: true,
+ schema: THIRD_DNS_SERVER_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_AUDIT, false),
+ }
+)]
+/// Read DNS settings.
+pub fn get_dns(
_param: Value,
_info: &ApiMethod,
- _rpcenv: &mut RpcEnvironment,
+ _rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
read_etc_resolv_conf()
}
-lazy_static! {
- pub static ref SEARCH_DOMAIN_SCHEMA: Arc<Schema> =
- StringSchema::new("Search domain for host-name lookup.").into();
-
- pub static ref FIRST_DNS_SERVER_SCHEMA: Arc<Schema> =
- StringSchema::new("First name server IP address.")
- .format(IP_FORMAT.clone()).into();
-
- pub static ref SECOND_DNS_SERVER_SCHEMA: Arc<Schema> =
- StringSchema::new("Second name server IP address.")
- .format(IP_FORMAT.clone()).into();
-
- pub static ref THIRD_DNS_SERVER_SCHEMA: Arc<Schema> =
- StringSchema::new("Third name server IP address.")
- .format(IP_FORMAT.clone()).into();
-}
-
-pub fn router() -> Router {
-
- let route = Router::new()
- .get(
- ApiMethod::new(
- get_dns,
- 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())
- .optional("dns3", THIRD_DNS_SERVER_SCHEMA.clone())
- )
- )
- .put(
- ApiMethod::new(
- update_dns,
- ObjectSchema::new("Returns DNS server IPs and sreach domain.")
- .required("search", SEARCH_DOMAIN_SCHEMA.clone())
- .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())
- ).protected(true)
- );
-
- route
-}
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_GET_DNS)
+ .put(&API_METHOD_UPDATE_DNS);