Skip to content

Commit

Permalink
feat: implement exporting data into a home directory (linux only for …
Browse files Browse the repository at this point in the history
…now)
  • Loading branch information
Chleba committed Jul 2, 2024
1 parent a98afed commit 170dd9a
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 28 deletions.
1 change: 1 addition & 0 deletions .config/config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"<f>": "Interface",
"<c>": "Clear",
"<s>": "Scan",
"<e>": "Export",
"<up>": "Up",
"<down>": "Down",
"<left>": "Left",
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ clap = { version = "4.4.5", features = [
color-eyre = "0.6.2"
config = "0.13.3"
crossterm = { version = "0.27.0", features = ["serde", "event-stream"] }
csv = "1.3.0"
derive_deref = "1.1.1"
directories = "5.0.1"
dns-lookup = "2.0.4"
Expand Down
32 changes: 32 additions & 0 deletions examples/env_csv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::{env, error::Error, io, process};

fn write_csv() -> Result<(), Box<dyn Error>> {
let mut csv_wtr = csv::Writer::from_writer(io::stdout());
csv_wtr.write_record(["maslo", "maslo1", "maslo2", "maslo3"])?;
csv_wtr.write_record(["1", "12", "maslo12", "maslo13"])?;
csv_wtr.write_record(["maslo", "maslo1", "maslo2", "maslo3"])?;

csv_wtr.flush()?;

Ok(())
}

fn main() {
println!("{:?}", env::vars_os());

match env::var_os("SUDO_USER") {
Some(val) => println!("SUDO_USER: {val:?}"),
None => println!("SUDO_USER was not found")
}

match env::var_os("HOME") {
Some(val) => println!("HOME: {val:?}"),
None => println!("HOME was not found")
}

if let Err(err) = write_csv() {
println!("{}", err);
process::exit(1);
}
}

5 changes: 4 additions & 1 deletion src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{fmt, net::Ipv4Addr};

use crate::{
components::{packetdump::ArpPacketData, wifi_scan::WifiInfo},
enums::{PacketTypeEnum, PacketsInfoTypesEnum, TabsEnum},
enums::{ExportData, PacketTypeEnum, PacketsInfoTypesEnum, TabsEnum},
mode::Mode,
};

Expand Down Expand Up @@ -49,6 +49,8 @@ pub enum Action {
PortScan(usize, u16),
PortScanDone(usize),
Clear,
Export,
ExportData(ExportData),
}

impl<'de> Deserialize<'de> for Action {
Expand Down Expand Up @@ -83,6 +85,7 @@ impl<'de> Deserialize<'de> for Action {
"Left" => Ok(Action::Left),
"Right" => Ok(Action::Right),
"Tab" => Ok(Action::Tab),
"Export" => Ok(Action::Export),
"JumpDiscovery" => Ok(Action::TabChange(TabsEnum::Discovery)),
"JumpPackets" => Ok(Action::TabChange(TabsEnum::Packets)),
"JumpPorts" => Ok(Action::TabChange(TabsEnum::Ports)),
Expand Down
48 changes: 37 additions & 11 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};

use crate::{
action::Action,
components::{
discovery::Discovery, title::Title, interfaces::Interfaces, packetdump::PacketDump,
tabs::Tabs, wifi_chart::WifiChart, wifi_interface::WifiInterface, wifi_scan::WifiScan,
ports::Ports, Component,
},
config::Config,
mode::Mode,
tui,
action::Action, components::{
discovery::{self, Discovery, ScannedIp},
export::Export,
interfaces::Interfaces,
packetdump::PacketDump,
ports::{Ports, ScannedIpPorts},
tabs::Tabs,
title::Title,
wifi_chart::WifiChart,
wifi_interface::WifiInterface,
wifi_scan::WifiScan,
Component,
}, config::Config, enums::ExportData, mode::Mode, tui
};

pub struct App {
Expand All @@ -37,10 +41,11 @@ impl App {
let wifiscan = WifiScan::default();
let wifi_interface = WifiInterface::default();
let wifi_chart = WifiChart::default();
let tabs = Tabs::default();
let tabs = Tabs::default();
let discovery = Discovery::default();
let packetdump = PacketDump::default();
let ports = Ports::default();
let export = Export::default();
let config = Config::new()?;

let mode = Mode::Normal;
Expand All @@ -59,6 +64,7 @@ impl App {
Box::new(discovery),
Box::new(packetdump),
Box::new(ports),
Box::new(export),
],
should_quit: false,
should_suspend: false,
Expand Down Expand Up @@ -133,7 +139,6 @@ impl App {
log::debug!("{action:?}");
}
match action {
// Action::AppModeChange(mode) => {
Action::AppModeChange(mode) => {
self.mode = mode;
}
Expand All @@ -143,6 +148,27 @@ impl App {
self.should_quit = true;
}

Action::Export => {
// get data from specific components by downcasting them and then try to
// comvert into specific struct
let mut scanned_ips: Vec<ScannedIp> = Vec::new();
let mut scanned_ports: Vec<ScannedIpPorts> = Vec::new();

for component in &self.components {
if let Some(d) = component.as_any().downcast_ref::<Discovery>() {
scanned_ips = d.get_scanned_ips().clone();
} else if let Some(pd) = component.as_any().downcast_ref::<PacketDump>() {
println!("maslo");
} else if let Some(p) = component.as_any().downcast_ref::<Ports>() {
scanned_ports = p.get_scanned_ports().clone();
}
}
action_tx.send(Action::ExportData(ExportData {
scanned_ips,
scanned_ports,
})).unwrap();
}

Action::Tick => {
self.last_tick_key_events.drain(..);
}
Expand Down
7 changes: 6 additions & 1 deletion src/components.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::any::Any;
use color_eyre::eyre::Result;
use crossterm::event::{KeyEvent, MouseEvent};
use ratatui::layout::Rect;
Expand All @@ -16,11 +17,12 @@ pub mod wifi_interface;
pub mod wifi_scan;
pub mod tabs;
pub mod ports;
pub mod export;

/// `Component` is a trait that represents a visual and interactive element of the user interface.
/// Implementors of this trait can be registered with the main application loop and will be able to receive events,
/// update state, and be rendered on the screen.
pub trait Component {
pub trait Component: Any {
/// Register an action handler that can send actions for processing if necessary.
/// # Arguments
/// * `tx` - An unbounded sender that can send actions.
Expand All @@ -31,6 +33,9 @@ pub trait Component {
Ok(())
}

#[allow(unused_variables)]
fn as_any(&self) -> &dyn Any;

#[allow(unused_variables)]
fn tab_changed(&mut self, tab: TabsEnum) -> Result<()> {
Ok(())
Expand Down
33 changes: 29 additions & 4 deletions src/components/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ static INPUT_SIZE: usize = 30;
static DEFAULT_IP: &str = "192.168.1.0/24";
const SPINNER_SYMBOLS: [&str; 6] = ["⠷", "⠯", "⠟", "⠻", "⠽", "⠾"];

#[derive(Clone)]
#[derive(Clone, Debug, PartialEq)]
pub struct ScannedIp {
pub ip: String,
pub mac: String,
Expand Down Expand Up @@ -98,6 +98,10 @@ impl Discovery {
}
}

pub fn get_scanned_ips(&self) -> &Vec<ScannedIp> {
&self.scanned_ips
}

fn set_cidr(&mut self, cidr_str: String, scan: bool) {
match &cidr_str.parse::<Ipv4Cidr>() {
Ok(ip_cidr) => {
Expand All @@ -120,8 +124,6 @@ impl Discovery {

fn send_arp(&mut self, target_ip: Ipv4Addr) {
let active_interface = self.active_interface.clone().unwrap();
// let active_interface_mac = active_interface.mac.unwrap()

if let Some(active_interface_mac) = active_interface.mac {
let ipv4 = active_interface.ips.iter().find(|f| f.is_ipv4()).unwrap();
let source_ip: Ipv4Addr = ipv4.ip().to_string().parse().unwrap();
Expand Down Expand Up @@ -377,6 +379,19 @@ impl Discovery {
.position(ratatui::widgets::block::Position::Top)
.alignment(Alignment::Right),
)
.title(
ratatui::widgets::block::Title::from(Line::from(vec![
Span::raw("|"),
Span::styled(
"e",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::styled("xport data", Style::default().fg(Color::Yellow)),
Span::raw("|"),
]))
.alignment(Alignment::Left)
.position(ratatui::widgets::block::Position::Bottom),
)
.title(
ratatui::widgets::block::Title::from(Line::from(vec![
Span::styled("|", Style::default().fg(Color::Yellow)),
Expand Down Expand Up @@ -499,6 +514,10 @@ impl Component for Discovery {
Ok(())
}

fn as_any(&self) -> &dyn std::any::Any {
self
}

fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
Expand Down Expand Up @@ -605,7 +624,11 @@ impl Component for Discovery {
// self.input.reset();
self.cidr_error = false;
}
self.action_tx.clone().unwrap().send(Action::AppModeChange(mode)).unwrap();
self.action_tx
.clone()
.unwrap()
.send(Action::AppModeChange(mode))
.unwrap();
self.mode = mode;
}
}
Expand Down Expand Up @@ -664,13 +687,15 @@ impl Component for Discovery {
input_size,
3,
);

// -- INPUT_SIZE - 3 is offset for border + 1char for cursor
let scroll = self.input.visual_scroll(INPUT_SIZE - 3);
let mut block = self.make_input(scroll);
if self.is_scanning {
block = block.clone().add_modifier(Modifier::DIM);
}
f.render_widget(block, input_rect);

// -- cursor
match self.mode {
Mode::Input => {
Expand Down
104 changes: 104 additions & 0 deletions src/components/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use color_eyre::eyre::{Ok, Result};
use csv::Writer;
use ratatui::{prelude::*, widgets::*};
use std::env;
use tokio::sync::mpsc::UnboundedSender;

use super::{discovery::ScannedIp, ports::ScannedIpPorts, Component, Frame};
use crate::action::Action;

#[derive(Default)]
pub struct Export {
action_tx: Option<UnboundedSender<Action>>,
home_dir: String,
}

impl Export {
pub fn new() -> Self {
Self {
action_tx: None,
home_dir: String::new(),
}
}

fn get_user_home_dir(&mut self) {
let mut home_dir = String::from("/root");
if let Some(h_dir) = env::var_os("HOME") {
home_dir = String::from(h_dir.to_str().unwrap());
}
if let Some(sudo_user) = env::var_os("SUDO_USER") {
home_dir = format!("/home/{}", sudo_user.to_str().unwrap());
}
self.home_dir = format!("{}/.netscanner", home_dir);

// -- create dot folder
if std::fs::metadata(self.home_dir.clone()).is_err() {
if std::fs::create_dir_all(self.home_dir.clone()).is_err() {
println!("Failed to create export dir");
}
}
}

pub fn write_discovery(&mut self, data: Vec<ScannedIp>) -> Result<()> {
let mut w = Writer::from_path(format!("{}/scanned_ips.csv", self.home_dir))?;

// -- header
w.write_record(["ip", "mac", "hostname", "vendor"])?;
for s_ip in data {
w.write_record([s_ip.ip, s_ip.mac, s_ip.hostname, s_ip.vendor])?;
}
w.flush()?;

Ok(())
}

pub fn write_ports(&mut self, data: Vec<ScannedIpPorts>) -> Result<()> {
let mut w = Writer::from_path(format!("{}/scanned_ports.csv", self.home_dir))?;

// -- header
w.write_record(["ip", "ports"])?;
for s_ip in data {
let ports: String = s_ip.ports.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(":");
w.write_record([s_ip.ip, ports])?;
}
w.flush()?;

Ok(())
}
}

impl Component for Export {
fn init(&mut self, area: Rect) -> Result<()> {
self.get_user_home_dir();
Ok(())
}

fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
}

fn as_any(&self) -> &dyn std::any::Any {
self
}

fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action {
Action::Export => {}
Action::ExportData(data) => {
let _ = self.write_discovery(data.scanned_ips);
let _ = self.write_ports(data.scanned_ports);
}
_ => {}
}
Ok(None)
}

fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> {
// let rect = Rect::new(0, 0, f.size().width, 1);
// let version: &str = env!("CARGO_PKG_VERSION");
// let title = format!(" Network Scanner (v{})", version);
// f.render_widget(Paragraph::new(title), rect);
Ok(())
}
}
Loading

0 comments on commit 170dd9a

Please sign in to comment.