Skip to content

Commit

Permalink
[feature] Eldritch file.follow (#546)
Browse files Browse the repository at this point in the history
* Implement file.follow

* Fix follow to only do one file

* Clean up follow

* Update contribution docs for current practice
  • Loading branch information
jabbate19 authored Feb 6, 2024
1 parent 8627d29 commit 8730337
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 17 deletions.
27 changes: 11 additions & 16 deletions docs/_docs/dev-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,20 @@ mod tests {
}
```

### `eldritch/lib.rs` tests
### `eldritch/runtime.rs` tests

Lastly you'll need to add your function to the `eldritch/lib.rs` integration test.
Lastly you'll need to add your function to the `eldritch/runtime.rs` integration test. Add your function to it's respective parent binding in alphabetical order.

```rust
#[test]
fn test_library_bindings() {
let globals = get_eldritch().unwrap();
let mut a = Assert::new();
a.globals(globals);
a.all_true(
r#"
dir(file) == ["append", "compress", "copy", "download", "exists", "hash", "is_dir", "is_file", "mkdir", "read", "remove", "rename", "replace", "replace_all", "template", "timestomp", "write"]
dir(process) == ["kill", "list", "name"]
dir(sys) == ["dll_inject", "exec", "is_linux", "is_macos", "is_windows", "shell"]
dir(pivot) == ["arp_scan", "bind_proxy", "ncat", "port_forward", "port_scan", "smb_exec", "ssh_exec", "ssh_password_spray"]
dir(assets) == ["copy","list"]
"#,
);
// Binding for the "file" functions
file_bindings: TestCase {
tome: Tome {
eldritch: String::from("print(dir(file))"),
parameters: HashMap::new(),
file_names: Vec::new(),
},
want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#),
want_error: None,
}
```

Expand Down
11 changes: 11 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,17 @@ The <b>file.download</b> method downloads a file at the URI specified in `uri` t

The <b>file.exists</b> method checks if a file or directory exists at the path specified.

### file.follow

`file.follow(path: str, fn: function(str)) -> None`

The <b>file.follow</b> method will call `fn(line)` for any new `line` that is added to the file (such as from `bash_history` and other logs).

```python
# Print every line added to bob's bash history
file.follow('/home/bob/.bash_history', print)
```

### file.is_dir

`file.is_dir(path: str) -> bool`
Expand Down
1 change: 1 addition & 0 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ md5 = "0.7.0"
netstat2 = "0.9.1"
network-interface = "1.0.1"
nix = "0.26.1"
notify = "6.1.1"
object = "0.31.1"
openssl = "0.10.55"
pnet = "0.34.0"
Expand Down
1 change: 1 addition & 0 deletions implants/lib/eldritch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ log = { workspace = true }
md5 = { workspace = true }
netstat2 = { workspace = true }
nix = { workspace = true }
notify = { workspace = true }
object = { workspace = true }
openssl = { workspace = true, features = ["vendored"] }
prost = { workspace = true}
Expand Down
7 changes: 7 additions & 0 deletions implants/lib/eldritch/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod copy_impl;
mod download_impl;
mod exists_impl;
mod find_impl;
mod follow_impl;
mod is_dir_impl;
mod is_file_impl;
mod list_impl;
Expand All @@ -23,6 +24,7 @@ use derive_more::Display;
use serde::{Serialize, Serializer};
use starlark::collections::SmallMap;
use starlark::environment::{Methods, MethodsBuilder, MethodsStatic};
use starlark::eval::Evaluator;
use starlark::values::dict::Dict;
use starlark::values::none::NoneType;
use starlark::values::{
Expand Down Expand Up @@ -181,4 +183,9 @@ fn methods(builder: &mut MethodsBuilder) {
if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); }
find_impl::find(path, name, file_type, permissions, modified_time, create_time)
}
fn follow<'v>(this: FileLibrary, path: String, f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result<NoneType> {
if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); }
follow_impl::follow(path, f, eval)?;
Ok(NoneType{})
}
}
38 changes: 38 additions & 0 deletions implants/lib/eldritch/src/file/follow_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::io::{Seek, BufReader, BufRead};
use notify::{Watcher, RecursiveMode, RecommendedWatcher, Config};

use anyhow::{anyhow, Result};
use starlark::{eval::Evaluator, values::Value};

pub fn follow<'v>(path: String, f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> Result<()> {
let starlark_heap = eval.heap();
// get pos to end of file
let mut file = std::fs::File::open(&path)?;
let mut pos = std::fs::metadata(&path)?.len();

// set up watcher
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?;

// watch
for _event in rx.into_iter().flatten() {
// ignore any event that didn't change the pos
if file.metadata()?.len() == pos {
continue;
}

// read from pos to end of file
file.seek(std::io::SeekFrom::Start(pos))?;

// update post to end of file
pos = file.metadata()?.len();

let reader = BufReader::new(&file);
for line in reader.lines() {
let val = starlark_heap.alloc(line?.to_string());
eval.eval_function(f, &[val], &[])?;
}
}
Ok(())
}
2 changes: 1 addition & 1 deletion implants/lib/eldritch/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ mod tests {
parameters: HashMap::new(),
file_names: Vec::new(),
},
want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#),
want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#),
want_error: None,
},
process_bindings: TestCase {
Expand Down

0 comments on commit 8730337

Please sign in to comment.