1mod port_details;
2
3use ansi_term::Colour;
4use futures::{lock::Mutex, stream, StreamExt};
5use indicatif::{ProgressBar, ProgressStyle};
6use itertools::Itertools;
7use port_details::*;
8use reqwest::{Body, Client, Method, Request, Url};
9use std::cmp::min;
10use std::fs::File;
11use std::{
12 fs, io,
13 io::{Error, Write},
14 net::{IpAddr, SocketAddr},
15 str::FromStr,
16 sync::Arc,
17 time::Duration,
18 time::Instant,
19};
20use tokio::{
21 io::{AsyncReadExt, AsyncWriteExt, Interest},
22 net::TcpStream,
23 net::UdpSocket,
24};
25use tokio_util::codec::{BytesCodec, FramedRead};
26
27#[derive(Copy, Clone, PartialEq, Eq)]
28pub enum TransportLayerProtocol {
29 Udp,
30 Tcp,
31 Sctp,
32 None,
33}
34
35pub struct FizzResult {
36 pub status_code: String,
37 pub headers: String,
38 pub body: String,
39}
40
41pub struct ExecRequest {
42 pub url: String,
43 pub user_agent: String,
44 pub verbose: bool,
45 pub disable_cert_validation: bool,
46 pub disable_hostname_validation: bool,
47 pub post_data: String,
48 pub http_method: Method,
49 pub progress_bar: bool,
50}
51
52pub async fn upload_file(
53 url: String,
54 path: String,
55 client: Client,
56 http_method: Method,
57) -> Result<FizzResult, reqwest::Error> {
58 let file = tokio::fs::File::open(path).await.unwrap();
59 let total_size = file.metadata().await.unwrap().len();
60 let target_url = url.clone();
61
62 let pb = ProgressBar::new(total_size);
64 pb.set_style(ProgressStyle::default_bar()
65 .template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
66 .progress_chars("#>-"));
67 pb.set_message(format!("Posting {}", url));
68
69 let mut uploaded = 0;
70
71 let mut reader_stream = FramedRead::new(file, BytesCodec::new());
72 let async_stream = async_stream::stream! {
73 while let Some(chunk) = reader_stream.next().await {
74 if let Ok(chunk) = &chunk {
75 let new = min(uploaded + (chunk.len() as u64), total_size);
76 uploaded = new;
77 pb.set_position(new);
78 }
79 yield chunk;
80 }
81 pb.finish_with_message(format!("Upload finished."));
82 };
83 let response = match http_method {
84 Method::POST => {
85 client
86 .post(target_url)
87 .body(Body::wrap_stream(async_stream))
88 .send()
89 .await
90 }
91 Method::PUT => {
92 client
93 .put(target_url)
94 .body(Body::wrap_stream(async_stream))
95 .send()
96 .await
97 }
98 _ => panic!("Can not upload the file with httpMethod:{}", http_method),
99 }
100 .unwrap();
101
102 let headers = response.headers().clone();
103 let status_code = response.status().to_string();
104 let result_text = response.text().await?;
105
106 return Ok(FizzResult {
107 status_code,
108 headers: format!("Headers:\n{:#?}", headers),
109 body: result_text,
110 });
111}
112
113pub async fn execute_request(exec: ExecRequest) -> Result<FizzResult, reqwest::Error> {
114 let client = reqwest::Client::builder()
115 .user_agent(exec.user_agent)
116 .danger_accept_invalid_certs(exec.disable_cert_validation)
117 .danger_accept_invalid_hostnames(exec.disable_hostname_validation)
118 .connection_verbose(exec.verbose)
119 .build()?;
120
121 let mut req = Request::new(exec.http_method.clone(), Url::from_str(&exec.url).unwrap());
122
123 if !exec.post_data.is_empty() {
124 let mut postdata = exec.post_data;
125 if postdata.starts_with('@') {
126 let file_name = postdata.split('@').last().unwrap();
127 log::info!("File opening for read:{}", file_name);
128 let file_size = fs::metadata(file_name).unwrap().len();
129
130 if file_size > 1_000_000 {
131 return upload_file(
133 exec.url,
134 String::from(file_name).clone(),
135 client,
136 exec.http_method,
137 )
138 .await;
139 }
140
141 let contents = fs::read(file_name).expect("Something went wrong reading the file");
142 unsafe {
143 postdata = String::from_utf8_unchecked(contents);
144 }
145 }
146 req.body_mut().replace(Body::from(postdata));
147 }
148
149 let res = client.execute(req).await.unwrap();
150 let headers = res.headers().clone();
151 let status_code = res.status().to_string();
152 let content_length = res.content_length().unwrap_or(0);
153
154 if content_length > 1_000_000 || exec.progress_bar {
155 let total_size = content_length;
157 let pb = ProgressBar::new(total_size);
158 pb.set_style(ProgressStyle::default_bar()
159 .template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
160 .progress_chars("#>-"));
161 pb.set_message(format!("Executing {}", exec.url));
162
163 let mut path = "frizz.out.file";
164 if exec.url.split('/').last().unwrap().chars().count() > 1 {
165 path = exec.url.split('/').last().unwrap();
166 };
167 let mut file = File::create(path).unwrap();
169 let mut downloaded: u64 = 0;
170 let mut stream = res.bytes_stream();
171
172 while let Some(item) = stream.next().await {
173 let chunk = item?;
174 file.write_all(&chunk).unwrap();
175 let new = min(downloaded + (chunk.len() as u64), total_size);
176 downloaded = new;
177 pb.set_position(new);
178 }
179
180 pb.finish_with_message(format!("Downloaded {} to {}", exec.url, path));
181
182 return Ok(FizzResult {
183 status_code,
184 headers: format!("Headers:\n{:#?}", headers),
185 body: format!("written to ./{}", path),
186 });
187 }
188
189 Ok(FizzResult {
190 status_code,
191 headers: format!("Headers:\n{:#?}", headers),
192 body: res.text().await?,
193 })
194}
195
196fn get_ports(
197 min_port: u16,
198 max_port: u16,
199 tl_protocol: TransportLayerProtocol,
200) -> (Box<dyn Iterator<Item = u16>>, u16) {
201 if min_port != max_port {
202 (Box::new(min_port..=max_port), max_port - min_port)
204 } else if tl_protocol != TransportLayerProtocol::None {
205 (
207 Box::new(get_most_common_ports(tl_protocol).into_iter()),
208 get_most_common_ports(tl_protocol).len() as u16,
209 )
210 } else {
211 let mut all_ports = get_most_common_ports(TransportLayerProtocol::Sctp);
213 all_ports.append(&mut get_most_common_ports(TransportLayerProtocol::Tcp));
214 all_ports.append(&mut get_most_common_ports(TransportLayerProtocol::Udp));
215 all_ports = all_ports.into_iter().unique().collect();
216
217 let count = all_ports.len();
218 (Box::new(all_ports.into_iter()), count as u16)
219 }
220}
221
222pub async fn scan(
223 target: IpAddr,
224 concurrency: usize,
225 timeout: u64,
226 min_port: u16,
227 max_port: u16,
228 tl_protocol: TransportLayerProtocol,
229 mut out_writer: Box<dyn Write>,
230) {
231 let (port_box, progress_size) = get_ports(min_port, max_port, tl_protocol);
232 let ports = stream::iter(port_box);
233 let output_values = Arc::new(Mutex::new(Vec::new()));
234 let before = Instant::now();
235
236 let pb = ProgressBar::new(progress_size.into());
237 pb.set_style(ProgressStyle::default_bar()
238 .template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>}/{len} ({percent}%, {eta})")
239 .progress_chars("##-"));
240
241 pb.set_message(format!(
242 "Scanning ports for {} min-max ports:{},{}",
243 target, min_port, max_port
244 ));
245
246 ports
247 .for_each_concurrent(concurrency, |port| {
248 let output_values = output_values.clone();
249 pb.inc(1);
250 async move {
251 let result = scan_port(target, port, timeout, tl_protocol).await;
252 if result > 0 {
253 output_values.lock().await.push(result);
254 }
255 }
256 })
257 .await;
258
259 pb.finish();
260 let d = port_details::get_details();
261 let details = d.lock().unwrap();
262 out_writer
263 .write(
264 Colour::Green
265 .paint("Port\tService\t\tProtocol\n".to_string())
266 .as_bytes(),
267 )
268 .ok();
269 for i in output_values.lock().await.iter() {
270 let d = match details.get(i) {
271 Some(detail) => detail,
272 None => "",
273 };
274 out_writer
275 .write(Colour::Blue.paint(format!("{:?}\t{:?}\n", i, d)).as_bytes())
276 .ok();
277 }
278
279 println!("Elapsed time to scan ports: {:.2?}", before.elapsed());
280}
281
282async fn scan_port(
283 target: IpAddr,
284 port: u16,
285 timeout: u64,
286 protocol: TransportLayerProtocol,
287) -> u16 {
288 let timeout = Duration::from_secs(timeout);
289 let socket_address = SocketAddr::new(target, port);
290
291 return match protocol {
292 TransportLayerProtocol::None
293 | TransportLayerProtocol::Sctp
294 | TransportLayerProtocol::Tcp => {
295 if let Ok(Ok(_)) =
296 tokio::time::timeout(timeout, TcpStream::connect(&socket_address)).await
297 {
298 return port;
299 }
300 0
301 }
302 TransportLayerProtocol::Udp => {
303 if let Ok(Ok(_)) = tokio::time::timeout(
304 timeout,
305 UdpSocket::bind("127.0.0.1:0")
306 .await
307 .expect("could not bind to address")
308 .connect(&socket_address),
309 )
310 .await
311 {
312 return port;
313 }
314 0
315 }
316 };
317}
318
319pub async fn open_socket_target(target: &str) -> Result<(), Error> {
320 log::info!("Socket connection");
321
322 let t_url = Url::parse(target).unwrap();
323 let addrs = t_url.socket_addrs(|| None).unwrap();
324 let mut stream = TcpStream::connect(&*addrs).await?;
325
326 loop {
327 let ready = stream
328 .ready(Interest::READABLE | Interest::WRITABLE)
329 .await?;
330 let mut data = vec![];
331 if ready.is_writable() {
332 let prompt = format!("{}{:?}{}", "Connected ", stream.peer_addr(), ">");
333 print!("{}", Colour::Green.paint(prompt));
334 io::stdout().flush().ok();
335
336 let mut input = String::new();
337 io::stdin().read_line(&mut input).ok();
338 if input.trim().eq_ignore_ascii_case("exit") {
339 return Ok(());
340 }
341 stream.write_all(input.as_bytes()).await?;
342 stream.read_to_end(&mut data).await?;
343 println!("Response:{:?}", String::from_utf8(data));
344 }
345 }
346}
347
348#[cfg(test)]
349#[allow(non_snake_case)]
350mod tests {
351 use ansi_term::Colour;
352 use std::path::Path;
353
354 use super::*;
355 use rstest::rstest;
356
357 #[tokio::test]
358 async fn test_download_upload_big_file() {
359 println!("download is starting.");
360 execute_request(ExecRequest {
361 url: "https://github.com/ozkanpakdil/rust-examples/files/7689196/s.zip".to_string(),
362 user_agent: "frizz".to_string(),
363 verbose: true,
364 disable_cert_validation: true,
365 disable_hostname_validation: true,
366 post_data: "".to_string(),
367 http_method: Method::GET,
368 progress_bar: true,
369 })
370 .await
371 .unwrap();
372 println!("download finished.");
373 if !Path::new("s.zip").is_file() {
374 panic!("s.zip not found, fail.");
375 }
376
377 execute_request(ExecRequest {
378 url: "https://bashupload.com/s.zip".to_string(),
379 user_agent: "frizz".to_string(),
380 verbose: false,
381 disable_cert_validation: true,
382 disable_hostname_validation: true,
383 post_data: "".to_string(),
384 http_method: Method::POST,
385 progress_bar: true,
386 })
387 .await
388 .unwrap();
389 println!("upload finished.");
390 fs::remove_file("s.zip").unwrap();
391 }
392
393 #[tokio::test]
394 async fn test_get_header() {
395 let res = execute_request(ExecRequest {
396 url: "http://httpbin.org/get".to_string(),
397 user_agent: "rusty".to_string(),
398 verbose: true,
399 disable_cert_validation: true,
400 disable_hostname_validation: true,
401 post_data: "".to_string(),
402 http_method: Method::GET,
403 progress_bar: true,
404 })
405 .await
406 .unwrap();
407 println!("{}", Colour::Red.paint(res.status_code));
408 println!("{}", Colour::Green.paint(res.headers));
409 println!("{}", Colour::Blue.paint(res.body));
410 fs::remove_file("./get").unwrap();
411 }
412
413 #[tokio::test]
414 #[should_panic]
415 async fn test_get_header_error() {
416 let res = execute_request(ExecRequest {
417 url: "htasxatp://httpbin.org/get".to_string(),
418 user_agent: "rusty".to_string(),
419 verbose: true,
420 disable_cert_validation: true,
421 disable_hostname_validation: true,
422 post_data: "".to_string(),
423 http_method: Method::GET,
424 progress_bar: true,
425 })
426 .await
427 .unwrap();
428 println!("{}", Colour::Red.paint(res.status_code));
429 println!("{}", Colour::Green.paint(res.headers));
430 println!("{}", Colour::Blue.paint(res.body));
431 }
432
433 #[rstest]
434 #[case(0, 10, TransportLayerProtocol::Tcp)]
435 #[case(0, 21, TransportLayerProtocol::Udp)]
436 #[case(0, 32, TransportLayerProtocol::Sctp)]
437 #[case(10, 100, TransportLayerProtocol::None)]
438 fn test_get_ports(
439 #[case] min_p: u16,
440 #[case] max_p: u16,
441 #[case] proto: TransportLayerProtocol,
442 ) {
443 let (_port_box, progress_size) = get_ports(min_p, max_p, proto);
444 match proto {
445 _ => assert_eq!(progress_size, max_p - min_p),
446 }
447 }
448}