1use obfstr::obfstr;
2
3use serde::Serialize;
4
5use colored::Colorize;
6use log::{info, debug, trace};
7use rayon::prelude::*;
8
9use std::fs;
10use std::fs::File;
11use std::io::{BufWriter, Write};
12use std::error::Error;
13
14use zip::write::{SimpleFileOptions, ZipWriter};
15
16extern crate zip;
17use crate::args::{Options, RUSTHOUND_VERSION};
18use crate::objects::common::{Meta, LdapObject};
19
20pub const BLOODHOUND_VERSION_4: i8 = 6;
22
23const SERIALIZE_BATCH: usize = 4096;
29
30enum Sink<'a> {
33 Zip { writer: &'a mut ZipWriter<BufWriter<File>>, datetime: &'a str, domain: &'a str },
34 Dir { path: &'a str, datetime: &'a str, domain: &'a str },
35}
36
37fn write_json_document<T, W>(writer: &mut W, name: &str, vec_json: &[T]) -> std::io::Result<()>
44where
45 T: LdapObject + Serialize + Sync,
46 W: Write,
47{
48 let count = vec_json.len();
49
50 writer.write_all(b"{\"data\":[")?;
51
52 let mut first = true;
53 for batch in vec_json.chunks(SERIALIZE_BATCH) {
54 let parts: Vec<String> = batch
56 .par_iter()
57 .map(|object| serde_json::to_string(object).expect("object serialization failed"))
58 .collect();
59
60 for part in parts {
61 if !first {
62 writer.write_all(b",")?;
63 }
64 first = false;
65 writer.write_all(part.as_bytes())?;
66 }
67 }
68
69 let meta = Meta::new(
70 0_i32,
71 name.to_owned(),
72 count as i32,
73 BLOODHOUND_VERSION_4,
74 format!("RustHound-CE v{}", RUSTHOUND_VERSION.to_owned()),
75 );
76 writer.write_all(b"],\"meta\":")?;
77 serde_json::to_writer(&mut *writer, &meta)?;
78 writer.write_all(b"}")?;
79
80 Ok(())
81}
82
83fn emit<T>(sink: &mut Sink, name: &str, vec_json: &[T]) -> Result<(), Box<dyn Error>>
86where
87 T: LdapObject + Serialize + Sync,
88{
89 if vec_json.is_empty() {
90 return Ok(());
91 }
92
93 let count = vec_json.len();
94 debug!("Making {name}.json");
95
96 match sink {
97 Sink::Zip { writer, datetime, domain } => {
98 let options = SimpleFileOptions::default()
102 .compression_method(zip::CompressionMethod::Deflated)
103 .large_file(true);
104 let filename = format!("{datetime}_{domain}_{name}.json");
106 trace!("Adding file {}", filename.bold());
107 writer.start_file(&filename, options)?;
108 write_json_document(*writer, name, vec_json)?;
109 }
110 Sink::Dir { path, datetime, domain } => {
111 let final_path = format!("{path}/{datetime}_{domain}_{name}.json");
112 let file = File::create(&final_path)?;
113 let mut buf = BufWriter::new(file);
114 write_json_document(&mut buf, name, vec_json)?;
115 buf.flush()?;
116 info!("{} created!", final_path.bold());
117 }
118 }
119
120 info!("{} {name} parsed!", count.to_string().bold());
121 Ok(())
122}
123
124macro_rules! emit_all {
126 ($sink:expr, $ad:expr) => {{
127 emit($sink, "users", &$ad.users)?;
128 emit($sink, "groups", &$ad.groups)?;
129 emit($sink, "computers", &$ad.computers)?;
130 emit($sink, "ous", &$ad.ous)?;
131 emit($sink, "domains", &$ad.domains)?;
132 emit($sink, "gpos", &$ad.gpos)?;
133 emit($sink, "containers", &$ad.containers)?;
134 emit($sink, "ntauthstores", &$ad.ntauthstores)?;
135 emit($sink, "aiacas", &$ad.aiacas)?;
136 emit($sink, "rootcas", &$ad.rootcas)?;
137 emit($sink, "enterprisecas", &$ad.enterprisecas)?;
138 emit($sink, "certtemplates", &$ad.certtemplates)?;
139 emit($sink, "issuancepolicies", &$ad.issuancepolicies)?;
140 }};
141}
142
143pub fn write_json_files(
145 datetime: &str,
146 domain_format: &str,
147 common_args: &Options,
148 ad_results: &crate::api::ADResults,
149) -> Result<(), Box<dyn Error>> {
150 fs::create_dir_all(&common_args.path)?;
151 let mut sink = Sink::Dir {
152 path: &common_args.path,
153 datetime,
154 domain: domain_format,
155 };
156 emit_all!(&mut sink, ad_results);
157 Ok(())
158}
159
160pub fn make_a_zip(
162 datetime: &str,
163 domain: &str,
164 path: &str,
165 ad_results: &crate::api::ADResults,
166) -> Result<String, Box<dyn Error>> {
167 fs::create_dir_all(path)?;
168 let final_path = format!("{path}/{datetime}_{domain}_{}.zip", obfstr!("rusthound-ce"));
169
170 let file = File::create(&final_path)?;
171 let mut writer = ZipWriter::new(BufWriter::with_capacity(1 << 20, file));
174
175 trace!("Making the ZIP file");
176 {
177 let mut sink = Sink::Zip { writer: &mut writer, datetime, domain };
178 emit_all!(&mut sink, ad_results);
179 }
180 writer.finish()?.flush()?;
181
182 info!("{} created!", &final_path.bold());
183 Ok(final_path)
184}