fapolicy_rules/
load.rs

1/*
2 * Copyright Concurrent Technologies Corporation 2021
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9use crate::error::Error;
10use crate::error::Error::MalformedFileMarker;
11use crate::load::RuleFrom::{Disk, Mem};
12use crate::parser::marker;
13use crate::parser::parse::StrTrace;
14use std::fs::File;
15use std::io::{BufRead, BufReader};
16use std::path::{Path, PathBuf};
17use std::{fs, io};
18
19pub(crate) type RuleSource = (PathBuf, String);
20
21pub(crate) enum RuleFrom {
22    Disk(PathBuf),
23    Mem(String),
24}
25
26pub fn rules_from_disk(path: &str) -> Result<Vec<RuleSource>, Error> {
27    rules_from(Disk(PathBuf::from(path)))
28}
29
30pub(crate) fn rules_from(src: RuleFrom) -> Result<Vec<RuleSource>, Error> {
31    let r = match src {
32        Disk(path) if path.is_dir() => rules_dir(&path)?,
33        Disk(path) => rules_file(path)?,
34        Mem(txt) => rules_text(txt)?,
35    };
36    Ok(r)
37}
38
39fn rules_file(path: PathBuf) -> Result<Vec<RuleSource>, io::Error> {
40    let reader = File::open(&path).map(BufReader::new)?;
41    let lines = reader
42        .lines()
43        .flatten()
44        .map(|s| (path.clone(), s))
45        .collect();
46    Ok(lines)
47}
48
49pub fn read_sorted_d_files(from: &Path) -> Result<Vec<PathBuf>, io::Error> {
50    let d_files: Result<Vec<PathBuf>, io::Error> =
51        fs::read_dir(from)?.map(|e| e.map(|e| e.path())).collect();
52
53    let mut d_files: Vec<PathBuf> = d_files?
54        .into_iter()
55        .filter(|p| p.is_file() && p.display().to_string().ends_with(".rules"))
56        .collect();
57
58    d_files.sort_by_key(|p| p.display().to_string());
59
60    Ok(d_files)
61}
62
63fn rules_dir(rules_source_path: &Path) -> Result<Vec<RuleSource>, io::Error> {
64    let d_files = read_sorted_d_files(rules_source_path)?;
65
66    let d_files: Result<Vec<(PathBuf, File)>, io::Error> = d_files
67        .into_iter()
68        .map(|p| (p.clone(), File::open(&p)))
69        .map(|(p, r)| r.map(|f| (p, f)))
70        .collect();
71
72    let d_files = d_files?.into_iter().map(|(p, f)| (p, BufReader::new(f)));
73
74    // todo;; externalize result flattening via expect here
75    let d_files = d_files.into_iter().map(|(path, rdr)| {
76        (
77            path.clone(),
78            rdr.lines()
79                .collect::<Result<Vec<String>, io::Error>>()
80                .unwrap_or_else(|_| {
81                    panic!("failed to read lines from rules file, {}", path.display())
82                }),
83        )
84    });
85
86    let d_files: Vec<RuleSource> = d_files
87        .into_iter()
88        .flat_map(|(src, lines)| {
89            lines
90                .iter()
91                .map(|l| (src.clone(), l.clone()))
92                .collect::<Vec<RuleSource>>()
93        })
94        .collect();
95
96    // todo;; revisit result flattening
97    Ok(d_files)
98}
99
100fn rules_text(rules_text: String) -> Result<Vec<RuleSource>, Error> {
101    let mut origin: Option<PathBuf> = Some(PathBuf::from("00-analyzer.rules"));
102    let mut lines = vec![];
103    for (num, line) in rules_text.split('\n').map(|s| s.trim()).enumerate() {
104        match marker::parse(StrTrace::new(line)) {
105            Ok((_, v)) => origin = Some(v),
106            Err(_) if line.starts_with('[') || line.ends_with(']') => {
107                // todo;; could improve the introspection of the parse error here to improve
108                //        the trace that is reported; the marker parser could also be reviewed
109                return Err(MalformedFileMarker(num + 1, line.trim().to_string()));
110            }
111            Err(_) => {
112                let r = origin
113                    .as_ref()
114                    .map(|p| (p.clone(), line.to_string()))
115                    .map(RuleSource::from)
116                    .unwrap();
117                lines.push(r);
118            }
119        };
120    }
121
122    // todo;; split the
123    Ok(lines)
124}
125
126#[cfg(test)]
127mod test {
128    use crate::error;
129    use crate::load::rules_text;
130    use std::collections::HashMap;
131    use std::path::PathBuf;
132
133    fn to_map(txt: &str) -> Result<HashMap<PathBuf, Vec<String>>, error::Error> {
134        let mut mapped: HashMap<PathBuf, Vec<String>> = HashMap::new();
135        for (p, r) in rules_text(txt.to_string())? {
136            if !mapped.contains_key(&p) {
137                mapped.insert(p.clone(), vec![]);
138            }
139            mapped.get_mut(&p).unwrap().push(r.clone());
140        }
141        Ok(mapped)
142    }
143
144    #[test]
145    fn text_single() -> Result<(), error::Error> {
146        let txt = r#"
147        [foo.rules]
148        allow perm=any all : all"#;
149        let r = to_map(txt)?;
150        assert!(r.contains_key(&PathBuf::from("foo.rules")));
151        Ok(())
152    }
153
154    #[test]
155    fn text_multi_file() -> Result<(), error::Error> {
156        let txt = r#"
157        [foo.rules]
158        allow perm=any all : all
159        [bar.rules]
160        allow perm=any all : all"#;
161        let r = to_map(txt)?;
162        assert!(r.contains_key(&PathBuf::from("foo.rules")));
163        assert!(r.contains_key(&PathBuf::from("bar.rules")));
164        Ok(())
165    }
166
167    #[test]
168    fn text_empty_file() -> Result<(), error::Error> {
169        let txt = r#"
170        [foo.rules]
171        [bar.rules]
172        allow perm=any all : all"#;
173        let r = to_map(txt)?;
174        assert!(!r.contains_key(&PathBuf::from("foo.rules")));
175        assert!(r.contains_key(&PathBuf::from("bar.rules")));
176        Ok(())
177    }
178}