osquery_rs/
lib.rs

1//! This crate allows you to execute osquery SQL queries using osquery Thrift API. You can execute osquery SQL query using one of the following methods:
2//! * Connect to the extension socket for an existing osquery instance
3//! * Spawn your own osquery instance and communicate with it using its extension socket
4//! Currently this crates only works on Linux. I am still working on Windows version.
5mod osquery_binding;
6use osquery_binding::{ExtensionResponse, TExtensionManagerSyncClient};
7use thrift;
8
9#[cfg(target_os = "windows")]
10use named_pipe::PipeClient;
11#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
12use std::os::unix::net::UnixStream;
13use std::{
14    io::Result,
15    ops::Drop,
16    process::{Child, Command, Stdio},
17    time::Duration,
18};
19use thrift::protocol::{TBinaryInputProtocol, TBinaryOutputProtocol};
20
21/// A Struct that handles osquery Thrif API comunication
22/// # Examples
23///
24/// ```
25/// use osquery_rs::OSQuery;
26///
27/// fn main () {
28///     let res = OSQuery::new()
29///             .set_socket("/home/root/.osquery/shell.em")
30///             .query(String::from("select * from time"))
31///             .unwrap();
32///     println!("{:#?}", res);
33/// }
34/// ```
35pub struct OSQuery {
36    _socket: String,
37    _socket_cleanup: bool,
38    _timeout: u64,
39    osquery_instance: Option<Child>,
40}
41
42impl OSQuery {
43    pub fn new() -> Self {
44        Self {
45            #[cfg(target_os = "windows")]
46            _socket: String::from(r"\\.\pipe\osquery-rs"),
47            #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
48            _socket: String::from("/tmp/osquery-rs"),
49            _socket_cleanup: false,
50            _timeout: 10,
51            osquery_instance: Option::None,
52        }
53    }
54
55    /// Set the osquery Thrift API socket to be used for comunication with osquery service
56    pub fn set_socket(mut self, path: &str) -> Self {
57        self._socket = String::from(path);
58        self
59    }
60
61    /// Set osquery queries timeout in seconds (default 10 seconds)
62    pub fn set_timeout(mut self, timeout: u64) -> Self {
63        self._timeout = timeout;
64        self
65    }
66
67    /// Get osquery queries timeout in seconds
68    pub fn get_timeout(&self) -> u64 {
69        self._timeout.clone()
70    }
71
72    /// A getter for socket used for comunication
73    pub fn get_socket(&self) -> String {
74        self._socket.clone()
75    }
76
77    /// Spawn an instance of osquery. This allows the use of osquery in system that does not have osquery installed (standalone)
78    /// # Examples
79    ///
80    /// ```
81    /// use osquery_rs::OSQuery;
82    ///
83    /// fn main() {
84    ///     let res = OSQuery::new()
85    ///         // Specify the path to the osquery binary
86    ///         .spawn_instance("./osqueryd")
87    ///         .unwrap()
88    ///         .query(String::from("select * from time"))
89    ///         .unwrap();
90    ///     println!("{:#?}", res);
91    /// }
92    /// ```
93    pub fn spawn_instance(mut self, path: &str) -> Result<Self> {
94        #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
95        {
96            let osquery_instance = Command::new(path)
97                .args([
98                    "--extensions_socket",
99                    &self._socket,
100                    "--disable_database",
101                    "--disable_watchdog",
102                    "--disable_logging",
103                    "--ephemeral",
104                    "--config_path",
105                    "/dev/null",
106                ])
107                .stdout(Stdio::null())
108                .stderr(Stdio::null())
109                .spawn()?;
110
111            // Wait until the socket is ready
112            loop {
113                match UnixStream::connect(&self._socket) {
114                    Ok(_) => break,
115                    Err(_) => continue,
116                };
117            }
118            self.osquery_instance = Some(osquery_instance);
119        }
120
121        #[cfg(target_os = "windows")]
122        {
123            println!("{:#?}", &self._socket);
124            println!("{:#?}", path);
125            let osquery_instance = Command::new(path)
126                .arg("--extensions_socket")
127                .arg(&self._socket)
128                .arg("--disable_database")
129                .arg("--disable_watchdog")
130                .arg("--disable_logging")
131                .arg("--ephemeral")
132                .arg("--config_path")
133                .arg("/dev/null")
134                .stdout(Stdio::null())
135                .stderr(Stdio::null())
136                .spawn()?;
137
138            // Wait until the socket is ready
139            loop {
140                match PipeClient::connect(&self._socket) {
141                    Ok(_) => break,
142                    Err(_) => continue,
143                };
144            }
145            self.osquery_instance = Some(osquery_instance);
146        }
147        self._socket_cleanup = true;
148        Ok(self)
149    }
150
151    /// Execute an osquery SQL query and retrive the results
152    pub fn query(&self, sql: String) -> Result<ExtensionResponse> {
153        #[cfg(target_os = "windows")]
154        let (reader, writer) = {
155            let mut reader = PipeClient::connect(&self._socket)?;
156            reader.set_read_timeout(Some(Duration::new(self._timeout, 0)));
157            reader.set_write_timeout(Some(Duration::new(self._timeout, 0)));
158            let mut writer = PipeClient::connect(&self._socket)?;
159            writer.set_read_timeout(Some(Duration::new(self._timeout, 0)));
160            writer.set_write_timeout(Some(Duration::new(self._timeout, 0)));
161            (reader, writer)
162        };
163        #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
164        let (reader, writer) = {
165            let reader = UnixStream::connect(&self._socket)?;
166            reader.set_read_timeout(Some(Duration::new(self._timeout, 0)))?;
167            reader.set_write_timeout(Some(Duration::new(self._timeout, 0)))?;
168            let writer = reader.try_clone()?;
169            (reader, writer)
170        };
171        let input_protocol = TBinaryInputProtocol::new(reader, false);
172        let output_protocol = TBinaryOutputProtocol::new(writer, false);
173        let mut extention_manager_client =
174            osquery_binding::ExtensionManagerSyncClient::new(input_protocol, output_protocol);
175        extention_manager_client.query(sql.clone()).map_err(|e| {
176            std::io::Error::new(
177                std::io::ErrorKind::InvalidInput,
178                format!("Unable to execute the query '{}', ERROR: {}", sql, e),
179            )
180        })
181    }
182}
183
184impl Drop for OSQuery {
185    fn drop(&mut self) {
186        match &mut self.osquery_instance {
187            Some(p) => {
188                p.kill()
189                    .expect(&format!("Unable to kill child process {}", p.id()));
190            }
191            None => {}
192        }
193        #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
194        {
195            if self._socket_cleanup {
196                std::fs::remove_file(&self._socket)
197                    .expect(&format!("Unable to remove socket '{}'", &self._socket));
198            }
199        }
200    }
201}