Skip to content

Commit

Permalink
🔧 Support <include>
Browse files Browse the repository at this point in the history
Implemented by iterating over the `Vec<Element>`, inspired by work by @elmarco , although this version doesn't have all the same features and validations as dbus2#23
  • Loading branch information
jokeyrhyme committed Nov 27, 2024
1 parent d2a1648 commit 052a8a1
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 4 deletions.
12 changes: 11 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{collections::HashSet, path::PathBuf};
use std::{
collections::HashSet,
path::{Path, PathBuf},
};

use anyhow::{Error, Result};
use serde::Deserialize;
Expand Down Expand Up @@ -90,6 +93,7 @@ impl TryFrom<Document> for Config {
bc.auth.insert(s);
}
Element::Fork => bc.fork = true,
Element::Include(_) => { /* no-op */ }
Element::KeepUmask => bc.keep_umask = true,
Element::Limit => {
warn!("busd does not implement `<limit>`");
Expand Down Expand Up @@ -142,6 +146,12 @@ impl Config {
let doc: Document = quick_xml::de::from_str(s)?;
Self::try_from(doc)
}

pub fn read_file(file_path: impl AsRef<Path>) -> Result<Self> {
// TODO: error message should contain file path to missing `<include>`
let doc = Document::read_file(&file_path)?;
Self::try_from(doc)
}
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
Expand Down
145 changes: 142 additions & 3 deletions src/config/xml.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use std::path::PathBuf;
use std::{
env::current_dir,
fs::read_to_string,
path::{Path, PathBuf},
str::FromStr,
};

use anyhow::{Error, Result};
use serde::Deserialize;
use tracing::{error, warn};

use super::{BusType, MessageType};

Expand All @@ -14,6 +21,93 @@ use super::{BusType, MessageType};
pub struct Document {
#[serde(rename = "$value", default)]
pub busconfig: Vec<Element>,
file_path: Option<PathBuf>,
}
impl FromStr for Document {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
quick_xml::de::from_str(s).map_err(Error::msg)
}
}
impl Document {
pub fn read_file(file_path: impl AsRef<Path>) -> Result<Document> {
let text = read_to_string(file_path.as_ref())?;

let mut doc = Document::from_str(&text)?;
doc.file_path = Some(file_path.as_ref().to_path_buf());
doc.resolve_includes()
}

fn resolve_includes(self) -> Result<Document> {
// TODO: implement protection against circular `<include>` references
let base_path = self.base_path()?;
let Document {
busconfig,
file_path,
} = self;

let mut doc = Document {
busconfig: vec![],
file_path: None,
};

for el in busconfig {
match el {
Element::Include(include) => {
let ignore_missing = include.ignore_missing == IncludeOption::Yes;
let file_path = match resolve_include_path(&base_path, &include.file_path) {
Ok(ok) => ok,
Err(err) => {
let msg = format!(
"'{}' should be a valid file path",
include.file_path.display()
);
if ignore_missing {
warn!(msg);
continue;
}
error!(msg);
return Err(err);
}
};
let mut included = match Document::read_file(&file_path) {
Ok(ok) => ok,
Err(err) => {
let msg = format!(
"'{}' should contain valid XML",
include.file_path.display()
);
if ignore_missing {
warn!(msg);
continue;
}
error!(msg);
return Err(err);
}
};
doc.busconfig.append(&mut included.busconfig);
}
_ => doc.busconfig.push(el),
}
}

doc.file_path = file_path;
Ok(doc)
}

fn base_path(&self) -> Result<PathBuf> {
match &self.file_path {
Some(some) => Ok(some
.parent()
.ok_or_else(|| Error::msg("`<include>` path should contain a file name"))?
.to_path_buf()),
None => {
warn!("ad-hoc document with unknown file path, using current working directory");
current_dir().map_err(Error::msg)
}
}
}
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
Expand All @@ -22,9 +116,9 @@ pub enum Element {
AllowAnonymous,
Auth(String),
Fork,
Include(IncludeElement),
// TODO: support `<includedir>`
KeepUmask,
// TODO: support `<include ignore_missing=(yes|no) if_selinux_enabled=(yes|no)
// selinux_root_relative=(yes|no)>` TODO: support `<includedir>`
Listen(String),
Limit,
Pidfile(PathBuf),
Expand All @@ -43,6 +137,29 @@ pub enum Element {
User(String),
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct IncludeElement {
#[serde(default, rename = "@ignore_missing")]
ignore_missing: IncludeOption,

// TODO: implement SELinux
#[serde(default, rename = "@if_selinux_enabled")]
if_selinux_enable: IncludeOption,
#[serde(default, rename = "@selinux_root_relative")]
selinux_root_relative: IncludeOption,

#[serde(rename = "$value")]
file_path: PathBuf,
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum IncludeOption {
#[default]
No,
Yes,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PolicyContext {
Expand Down Expand Up @@ -137,3 +254,25 @@ pub struct TypeElement {
#[serde(rename = "$text")]
pub r#type: BusType,
}

fn resolve_include_path(
base_path: impl AsRef<Path>,
include_path: impl AsRef<Path>,
) -> Result<PathBuf> {
let p = include_path.as_ref();
if p.is_absolute() {
return p.canonicalize().map_err(Error::msg);
}

error!(
"resolve_include_path: {} {}",
&base_path.as_ref().display(),
&include_path.as_ref().display()
);

base_path
.as_ref()
.join(p)
.canonicalize()
.map_err(Error::msg)
}
65 changes: 65 additions & 0 deletions tests/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::collections::HashSet;

use busd::config::{Access, Config, Name, Operation, OwnOperation, Policy};

#[test]
fn config_read_file_with_includes_ok() {
let got =
Config::read_file("./tests/fixtures/valid.conf").expect("should read and parse XML input");

assert_eq!(
got,
Config {
auth: HashSet::from_iter(vec![String::from("ANONYMOUS"), String::from("EXTERNAL"),]),
listen: HashSet::from_iter(vec![
String::from("unix:path=/tmp/foo"),
String::from("tcp:host=localhost,port=1234"),
]),
policies: vec![
Policy::DefaultContext(vec![
(
Access::Allow,
Operation::Own(OwnOperation {
own: Some(Name::Any)
})
),
(
Access::Deny,
Operation::Own(OwnOperation {
own: Some(Name::Any)
})
),
]),
Policy::MandatoryContext(vec![
(
Access::Deny,
Operation::Own(OwnOperation {
own: Some(Name::Any)
})
),
(
Access::Allow,
Operation::Own(OwnOperation {
own: Some(Name::Any)
})
),
],),
],
..Default::default()
}
);
}

#[should_panic]
#[test]
fn config_read_file_with_missing_include_err() {
Config::read_file("./tests/fixtures/missing_include.conf")
.expect("should read and parse XML input");
}

#[should_panic]
#[test]
fn config_read_file_with_transitive_missing_include_err() {
Config::read_file("./tests/fixtures/transitive_missing_include.conf")
.expect("should read and parse XML input");
}
5 changes: 5 additions & 0 deletions tests/fixtures/missing_include.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<include>./missing.conf</include>
</busconfig>
5 changes: 5 additions & 0 deletions tests/fixtures/transitive_missing_include.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<include>./missing_include.conf</include>
</busconfig>
12 changes: 12 additions & 0 deletions tests/fixtures/valid.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<auth>ANONYMOUS</auth>
<listen>unix:path=/tmp/foo</listen>
<policy context="default">
<allow own="*"/>
<deny own="*"/>
</policy>
<include>./valid_included.conf</include>
<include ignore_missing="yes">./valid_missing.conf</include>
</busconfig>
10 changes: 10 additions & 0 deletions tests/fixtures/valid_included.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<auth>EXTERNAL</auth>
<listen>tcp:host=localhost,port=1234</listen>
<policy context="mandatory">
<deny own="*"/>
<allow own="*"/>
</policy>
</busconfig>

0 comments on commit 052a8a1

Please sign in to comment.