mirror of
https://github.com/bee-san/RustScan.git
synced 2026-11-08 08:03:06 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f994361f |
No files matched your search
+21
-19
@@ -1,6 +1,8 @@
|
||||
name: CD Pipeline
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-nix:
|
||||
@@ -37,11 +39,11 @@ jobs:
|
||||
path: target/aarch64-unknown-linux-gnu/release/rustscan
|
||||
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
command: build
|
||||
target: ${{ matrix.target }}
|
||||
@@ -52,11 +54,11 @@ jobs:
|
||||
if: matrix.type == 'ubuntu-x64'
|
||||
run: |
|
||||
tar czf ${{ matrix.name }}.tar.gz -C target/x86_64-unknown-linux-musl/release rustscan
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.type == 'ubuntu-x64'
|
||||
with:
|
||||
name: ${{ matrix.name }}.tar.gz
|
||||
@@ -67,7 +69,7 @@ jobs:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install cargo-deb
|
||||
run: cargo install -f cargo-deb
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
@@ -79,7 +81,7 @@ jobs:
|
||||
- name: Deb Build
|
||||
run: cargo deb --target=x86_64-unknown-linux-musl
|
||||
- name: Upload Deb Artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rustscan.deb
|
||||
path: ./target/x86_64-unknown-linux-musl/debian/*
|
||||
@@ -90,11 +92,11 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
command: build
|
||||
target: x86_64-apple-darwin
|
||||
@@ -104,11 +106,11 @@ jobs:
|
||||
- name: Build tar.gz for homebrew installs
|
||||
run: |
|
||||
tar czf x86_64-macos-rustscan.tar.gz -C target/x86_64-apple-darwin/release rustscan
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x86_64-macos-rustscan
|
||||
path: target/x86_64-apple-darwin/release/rustscan
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x86_64-macos-rustscan.tar.gz
|
||||
path: x86_64-macos-rustscan.tar.gz
|
||||
@@ -119,11 +121,11 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
command: build
|
||||
target: aarch64-apple-darwin
|
||||
@@ -133,11 +135,11 @@ jobs:
|
||||
- name: Build tar.gz for homebrew installs
|
||||
run: |
|
||||
tar czf aarch64-macos-rustscan.tar.gz -C target/aarch64-apple-darwin/release rustscan
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: aarch64-macos-rustscan
|
||||
path: target/aarch64-apple-darwin/release/rustscan
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: aarch64-macos-rustscan.tar.gz
|
||||
path: aarch64-macos-rustscan.tar.gz
|
||||
@@ -162,18 +164,18 @@ jobs:
|
||||
name: x86-windows-rustscan.exe
|
||||
path: target\i686-pc-windows-msvc\release\rustscan.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
command: build
|
||||
target: ${{ matrix.target }}
|
||||
args: "--locked --release"
|
||||
strip: true
|
||||
toolchain: stable
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Dependabot Auto-Merge
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: |
|
||||
gh pr review --approve "$PR_URL"
|
||||
gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Check Markdown links
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
schedule:
|
||||
# Run everyday at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07)
|
||||
- cron: "0 9 * * *"
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
config-file: './.github/workflows/mlc_config.json'
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v4
|
||||
uses: extractions/setup-just@v2
|
||||
|
||||
- name: Run just
|
||||
run: just test
|
||||
@@ -1,8 +1,6 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
# generated file by build.rs for mapping nmap-payloads to rust
|
||||
src/generated.rs
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
|
||||
Generated
+364
-1018
File diff suppressed because it is too large.
Load diff
+16
-16
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustscan"
|
||||
version = "2.4.1"
|
||||
version = "2.3.0"
|
||||
authors = ["Autumn <[email protected]>"]
|
||||
edition = "2018"
|
||||
description = "Faster Nmap Scanning with Rust"
|
||||
@@ -17,33 +17,33 @@ exclude = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.6.1", features = ["derive", "wrap_help"] }
|
||||
colored = "3.1.1"
|
||||
async-std = "1.13.2"
|
||||
clap = { version = "4.5.13", features = ["derive", "wrap_help"] }
|
||||
colored = "2.1.0"
|
||||
async-std = "1.13.0"
|
||||
futures = "0.3"
|
||||
rlimit = "0.11.0"
|
||||
log = "0.4.32"
|
||||
env_logger = "0.11.10"
|
||||
anstream = "=1.0.0"
|
||||
dirs = "7.0.0"
|
||||
rlimit = "0.10.2"
|
||||
log = "0.4.22"
|
||||
env_logger = "0.11.5"
|
||||
anstream = "=0.6.18"
|
||||
dirs = "5.0.1"
|
||||
gcd = "2.0.1"
|
||||
rand = "0.10.1"
|
||||
rand = "0.8.5"
|
||||
colorful = "0.3.2"
|
||||
ansi_term = "0.12.1"
|
||||
toml = "1.1.2"
|
||||
toml = "0.8.19"
|
||||
serde = "1.0.124"
|
||||
serde_derive = "1.0.116"
|
||||
cidr-utils = "0.7.1"
|
||||
itertools = "0.15.0"
|
||||
hickory-resolver = { version = "0.24.3", features = ["dns-over-rustls"] }
|
||||
cidr-utils = "0.6.1"
|
||||
itertools = "0.13.0"
|
||||
hickory-resolver = { version = "0.24.0", features = ["dns-over-rustls"] }
|
||||
anyhow = "1.0.40"
|
||||
text_placeholder = { version = "0.5", features = ["struct_context"] }
|
||||
once_cell = "1.21.4"
|
||||
once_cell = "1.20.2"
|
||||
|
||||
[dev-dependencies]
|
||||
parameterized = "2.0.0"
|
||||
wait-timeout = "0.2"
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto, nmap"
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Build Stage
|
||||
FROM rust:alpine as builder
|
||||
LABEL maintainer="RustScan <https://github.com/RustScan>"
|
||||
RUN apk add --no-cache build-base
|
||||
|
||||
# Encourage some layer caching here rather then copying entire directory that includes docs to builder container ~CMN
|
||||
WORKDIR /app/rustscan
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src/ src/
|
||||
RUN cargo build --release
|
||||
|
||||
# Release Stage
|
||||
FROM alpine:3.20.3 as release
|
||||
LABEL author="Hydragyrum <https://github.com/Hydragyrum>"
|
||||
LABEL author="LeoFVO <https://github.com/LeoFVO>"
|
||||
|
||||
RUN addgroup -S rustscan && \
|
||||
adduser -S -G rustscan rustscan && \
|
||||
ulimit -n 100000 && \
|
||||
apk add --no-cache nmap nmap-scripts wget ca-certificates bind-tools
|
||||
|
||||
USER rustscan
|
||||
COPY --from=builder /app/rustscan/target/release/rustscan /usr/local/bin/rustscan
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/rustscan" ]
|
||||
@@ -19,10 +19,6 @@ The Modern Port Scanner. **Find ports quickly (3 seconds at its fastest)**. Run
|
||||
|
||||
# 🛠️ Installation
|
||||
|
||||
You can install RustScan's binary from our [releases page](https://github.com/RustScan/RustScan/releases).
|
||||
|
||||
We would prefer you to install with a package manager so it is tested and works for your system.
|
||||
|
||||
RustScan is in many repositories already. Install it with whatever tools you wish:
|
||||
|
||||
[](https://repology.org/project/rustscan/versions)
|
||||
@@ -40,7 +36,7 @@ MacOS:
|
||||
Arch:
|
||||
|
||||
```
|
||||
pacman -S rustscan
|
||||
yay rustscan
|
||||
```
|
||||
|
||||
# ✨ Features
|
||||
@@ -55,7 +51,7 @@ Arch:
|
||||
|
||||
| <!--Installation Guide--> | <!--Documentation--> | <!--Discord--> |
|
||||
| :----------------------------------------: | :------------------------------------: | :-------------------------: |
|
||||
| :book: [Installation Guide][toc-install] | :books: [Documentation][links-table-2] | :parrot: [Discord][discord] |
|
||||
| :book: [Installation Guide][links-table-1] | :books: [Documentation][links-table-2] | :parrot: [Discord][discord] |
|
||||
|
||||
## 🙋 Table of Contents
|
||||
|
||||
@@ -209,7 +205,7 @@ This project follows the [all-contributors](https://github.com/all-contributors/
|
||||
[speed-3]: https://github.com/RustScan/RustScan/wiki/Increasing-Speed-&-Accuracy "Increasing Speed & Accuracy"
|
||||
[toc-community]: https://github.com/RustScan/RustScan#-community "Community"
|
||||
[links-table-1]: https://github.com/RustScan/RustScan#-full-installation-guide "Full installation guide"
|
||||
[links-table-2]: https://github.com/bee-san/RustScan/wiki "Rustscan"
|
||||
[links-table-2]: https://rustscan.github.io/RustScan/ "Rustscan"
|
||||
[discord]: http://discord.skerritt.blog "Discord blog"
|
||||
[toc-install]: https://github.com/RustScan/RustScan/wiki/Installation-Guide "Installation Guide Wiki"
|
||||
[toc-docker-usage]: https://github.com/RustScan/RustScan/wiki/Installation-Guide#docker- "Docker Installation Guide Wiki"
|
||||
@@ -232,4 +228,4 @@ This project follows the [all-contributors](https://github.com/all-contributors/
|
||||
[badge-3]: https://img.shields.io/github/downloads/rustscan/rustscan/total?label=GitHub%20Downloads
|
||||
[badge-4]: https://img.shields.io/crates/d/rustscan?label=Cargo%20Downloads
|
||||
[badge-5]: https://img.shields.io/discord/754001738184392704
|
||||
[badge-6]: https://github.com/RustScan/RustScan/actions/workflows/build.yml/badge.svg?branch=master
|
||||
[badge-6]: https://github.com/RustScan/RustScan/workflows/Build/badge.svg?branch=master
|
||||
@@ -1,105 +1,16 @@
|
||||
use async_std::task::block_on;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use rustscan::input::{Opts, PortRange, ScanOrder};
|
||||
use rustscan::port_strategy::PortStrategy;
|
||||
use rustscan::scanner::Scanner;
|
||||
use std::hint::black_box;
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
fn portscan_tcp(scanner: &Scanner) {
|
||||
let _scan_result = block_on(scanner.run());
|
||||
}
|
||||
|
||||
fn portscan_udp(scanner: &Scanner) {
|
||||
let _scan_result = block_on(scanner.run());
|
||||
}
|
||||
|
||||
fn bench_address() {
|
||||
let _addrs = ["127.0.0.1".parse::<IpAddr>().unwrap()];
|
||||
}
|
||||
|
||||
fn bench_port_strategy() {
|
||||
let range = PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
|
||||
}
|
||||
|
||||
fn bench_address_parsing() {
|
||||
let opts = Opts {
|
||||
addresses: vec![
|
||||
"127.0.0.1".to_owned(),
|
||||
"10.2.0.1".to_owned(),
|
||||
"192.168.0.0/24".to_owned(),
|
||||
],
|
||||
exclude_addresses: Some(vec![
|
||||
"10.0.0.0/8".to_owned(),
|
||||
"172.16.0.0/12".to_owned(),
|
||||
"192.168.0.0/16".to_owned(),
|
||||
"172.16.0.1".to_owned(),
|
||||
]),
|
||||
..Default::default()
|
||||
};
|
||||
let _ips = rustscan::address::parse_addresses(&opts);
|
||||
fn fibonacci(n: u64) -> u64 {
|
||||
match n {
|
||||
0 => 1,
|
||||
1 => 1,
|
||||
n => fibonacci(n - 1) + fibonacci(n - 2),
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
|
||||
let range = PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy_tcp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
|
||||
let strategy_udp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
|
||||
|
||||
let scanner_tcp = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(10),
|
||||
1,
|
||||
false,
|
||||
strategy_tcp,
|
||||
true,
|
||||
vec![],
|
||||
false,
|
||||
);
|
||||
|
||||
c.bench_function("portscan tcp", |b| {
|
||||
b.iter(|| portscan_tcp(black_box(&scanner_tcp)))
|
||||
});
|
||||
|
||||
let scanner_udp = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(10),
|
||||
1,
|
||||
false,
|
||||
strategy_udp,
|
||||
true,
|
||||
vec![],
|
||||
true,
|
||||
);
|
||||
|
||||
let mut udp_group = c.benchmark_group("portscan udp");
|
||||
udp_group.measurement_time(Duration::from_secs(20));
|
||||
udp_group.bench_function("portscan udp", |b| {
|
||||
b.iter(|| portscan_udp(black_box(&scanner_udp)))
|
||||
});
|
||||
udp_group.finish();
|
||||
|
||||
// Benching helper functions
|
||||
c.bench_function("parse address", |b| b.iter(bench_address));
|
||||
|
||||
c.bench_function("port strategy", |b| b.iter(bench_port_strategy));
|
||||
|
||||
let mut address_group = c.benchmark_group("address parsing");
|
||||
address_group.measurement_time(Duration::from_secs(10));
|
||||
address_group.bench_function("parse addresses with exclusions", |b| {
|
||||
b.iter(bench_address_parsing)
|
||||
});
|
||||
address_group.finish();
|
||||
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
|
||||
@@ -141,7 +141,7 @@ fn ports_v(fp_map: &BTreeMap<i32, String>) -> BTreeMap<i32, Vec<u16>> {
|
||||
} else if !segment.is_empty() {
|
||||
match segment.parse::<u16>() {
|
||||
Ok(port) => port_list.push(port),
|
||||
Err(_) => println!("Error parsing port: {segment}"),
|
||||
Err(_) => println!("Error parsing port: {}", segment),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ fn parser(payload: &str) -> Vec<u8> {
|
||||
for (idx, char) in payload.chars().enumerate() {
|
||||
if char == '\\' && payload.chars().nth(idx + 1) == Some('x') {
|
||||
continue;
|
||||
} else if char.is_ascii_hexdigit() {
|
||||
} else if char.is_ascii_digit() {
|
||||
tmp_str.push(char);
|
||||
if tmp_str.len() == 2 {
|
||||
bytes.push(u8::from_str_radix(&tmp_str, 16).unwrap());
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Dockerfile for RustScan development environment
|
||||
# Provides a containerized setup with Rust, nmap, and development tools
|
||||
FROM rust
|
||||
# Install nmap first.
|
||||
RUN apt-get update -qy && apt-get install -qy nmap
|
||||
# Then install rustfmt for code formatting and clippy for linting.
|
||||
# Then install rustfmt and clippy for cargo.
|
||||
RUN rustup component add rustfmt clippy
|
||||
@@ -1,4 +0,0 @@
|
||||
tags = ["core_approved", "example"]
|
||||
ports = ["80", "443", "8080"]
|
||||
developer = ["example", "https://example.org"]
|
||||
directory = "fixtures/.rustscan_scripts"
|
||||
@@ -1,7 +1,6 @@
|
||||
test:
|
||||
cargo test
|
||||
cargo nextest run
|
||||
cargo clippy -- --deny warnings
|
||||
cargo clippy --tests -- --deny warnings
|
||||
cargo fmt --check
|
||||
cargo doc --workspace --all-features --no-deps --document-private-items
|
||||
|
||||
|
||||
+39
-176
@@ -6,7 +6,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use cidr_utils::cidr::{IpCidr, IpInet};
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
use hickory_resolver::{
|
||||
config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts},
|
||||
Resolver,
|
||||
@@ -69,13 +69,10 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
let excluded_cidrs = parse_excluded_networks(&input.exclude_addresses, &backup_resolver);
|
||||
|
||||
// Remove duplicated/excluded IPs.
|
||||
let mut seen = BTreeSet::new();
|
||||
ips.retain(|ip| seen.insert(*ip) && !excluded_cidrs.iter().any(|cidr| cidr.contains(ip)));
|
||||
|
||||
ips
|
||||
ips.into_iter()
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Given a string, parse it as a host, IP address, or CIDR.
|
||||
@@ -92,21 +89,16 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
|
||||
/// let ips = parse_address("127.0.0.1", &Resolver::default().unwrap());
|
||||
/// ```
|
||||
pub fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
|
||||
if let Ok(addr) = IpAddr::from_str(address) {
|
||||
// `address` is an IP string
|
||||
vec![addr]
|
||||
} else if let Ok(net_addr) = IpInet::from_str(address) {
|
||||
// `address` is a CIDR string
|
||||
net_addr.network().into_iter().addresses().collect()
|
||||
} else {
|
||||
// `address` is a hostname or DNS name
|
||||
// attempt default DNS lookup
|
||||
match format!("{address}:80").to_socket_addrs() {
|
||||
Ok(mut iter) => vec![iter.next().unwrap().ip()],
|
||||
// default lookup didn't work, so try again with the dedicated resolver
|
||||
Err(_) => resolve_ips_from_host(address, resolver),
|
||||
}
|
||||
}
|
||||
IpCidr::from_str(address)
|
||||
.map(|cidr| cidr.iter().map(|c| c.address()).collect())
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
format!("{}:{}", &address, 80)
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.map(|mut iter| vec![iter.next().unwrap().ip()])
|
||||
})
|
||||
.unwrap_or_else(|| resolve_ips_from_host(address, resolver))
|
||||
}
|
||||
|
||||
/// Uses DNS to get the IPS associated with host
|
||||
@@ -124,46 +116,6 @@ fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr
|
||||
ips
|
||||
}
|
||||
|
||||
/// Parses excluded networks from a list of addresses.
|
||||
///
|
||||
/// This function handles three types of inputs:
|
||||
/// 1. CIDR notation (e.g. "192.168.0.0/24")
|
||||
/// 2. Single IP addresses (e.g. "192.168.0.1")
|
||||
/// 3. Hostnames that need to be resolved (e.g. "example.com")
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rustscan::address::parse_excluded_networks;
|
||||
/// # use hickory_resolver::Resolver;
|
||||
/// let resolver = Resolver::default().unwrap();
|
||||
/// let excluded = parse_excluded_networks(&Some(vec!["192.168.0.0/24".to_owned()]), &resolver);
|
||||
/// ```
|
||||
pub fn parse_excluded_networks(
|
||||
exclude_addresses: &Option<Vec<String>>,
|
||||
resolver: &Resolver,
|
||||
) -> Vec<IpCidr> {
|
||||
exclude_addresses
|
||||
.iter()
|
||||
.flatten()
|
||||
.flat_map(|addr| parse_single_excluded_address(addr, resolver))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Parses a single address into an IpCidr, handling CIDR notation, IP addresses, and hostnames.
|
||||
fn parse_single_excluded_address(addr: &str, resolver: &Resolver) -> Vec<IpCidr> {
|
||||
if let Ok(cidr) = IpCidr::from_str(addr) {
|
||||
return vec![cidr];
|
||||
}
|
||||
|
||||
if let Ok(ip) = IpAddr::from_str(addr) {
|
||||
return vec![IpCidr::new_host(ip)];
|
||||
}
|
||||
|
||||
resolve_ips_from_host(addr, resolver)
|
||||
.into_iter()
|
||||
.map(IpCidr::new_host)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Derive a DNS resolver.
|
||||
///
|
||||
/// 1. if the `resolver` parameter has been set:
|
||||
@@ -241,11 +193,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_correct_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
@@ -260,71 +209,10 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_addresses_with_address_exclusions() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["192.168.0.0/30".to_owned()],
|
||||
exclude_addresses: Some(vec!["192.168.0.1".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
ips,
|
||||
[
|
||||
Ipv4Addr::new(192, 168, 0, 0),
|
||||
Ipv4Addr::new(192, 168, 0, 2),
|
||||
Ipv4Addr::new(192, 168, 0, 3)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_addresses_with_cidr_exclusions() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["192.168.0.0/29".to_owned()],
|
||||
exclude_addresses: Some(vec!["192.168.0.0/30".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
ips,
|
||||
[
|
||||
Ipv4Addr::new(192, 168, 0, 4),
|
||||
Ipv4Addr::new(192, 168, 0, 5),
|
||||
Ipv4Addr::new(192, 168, 0, 6),
|
||||
Ipv4Addr::new(192, 168, 0, 7),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_addresses_with_incorrect_address_exclusions() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["192.168.0.0/30".to_owned()],
|
||||
exclude_addresses: Some(vec!["192.168.0.1".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
ips,
|
||||
[
|
||||
Ipv4Addr::new(192, 168, 0, 0),
|
||||
Ipv4Addr::new(192, 168, 0, 2),
|
||||
Ipv4Addr::new(192, 168, 0, 3)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_host_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["google.com".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["google.com".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 1);
|
||||
@@ -332,11 +220,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_correct_and_incorrect_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips, [Ipv4Addr::new(127, 0, 0, 1),]);
|
||||
@@ -344,61 +229,43 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_incorrect_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert!(ips.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_hosts_file_and_incorrect_hosts() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let opts = Opts {
|
||||
addresses: vec!["fixtures/hosts.txt".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_hosts_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let opts = Opts {
|
||||
addresses: vec!["fixtures/empty_hosts.txt".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/empty_hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_naughty_host_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let opts = Opts {
|
||||
addresses: vec!["fixtures/naughty_string.txt".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/naughty_string.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_duplicate_cidrs() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["79.98.104.0/21".to_owned(), "79.98.104.0/24".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["79.98.104.0/21".to_owned(), "79.98.104.0/24".to_owned()];
|
||||
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
@@ -406,25 +273,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_overspecific_cidr() {
|
||||
// a canonical CIDR string has 0 in all host bits, but we want to treat any CIDR-like string as CIDR
|
||||
let opts = Opts {
|
||||
addresses: vec!["192.128.1.1/24".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
fn resolver_default_cloudflare() {
|
||||
let opts = Opts::default();
|
||||
|
||||
let ips = parse_addresses(&opts);
|
||||
let resolver = get_resolver(&opts.resolver);
|
||||
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
|
||||
|
||||
assert_eq!(ips.len(), 256);
|
||||
assert!(opts.resolver.is_none());
|
||||
assert!(lookup.iter().next().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_args_google_dns() {
|
||||
let mut opts = Opts::default();
|
||||
// https://developers.google.com/speed/public-dns
|
||||
let opts = Opts {
|
||||
resolver: Some("8.8.8.8,8.8.4.4".to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
opts.resolver = Some("8.8.8.8,8.8.4.4".to_owned());
|
||||
|
||||
let resolver = get_resolver(&opts.resolver);
|
||||
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
|
||||
|
||||
@@ -42,11 +42,13 @@ impl Benchmark {
|
||||
let mut summary = String::from("\nRustScan Benchmark Summary");
|
||||
|
||||
for timer in &self.named_timers {
|
||||
if let Some(start) = timer.start {
|
||||
if let Some(end) = timer.end {
|
||||
let runtime_secs = end.saturating_duration_since(start).as_secs_f32();
|
||||
summary.push_str(&format!("\n{0: <10} | {1: <10}s", timer.name, runtime_secs));
|
||||
}
|
||||
if timer.start.is_some() && timer.end.is_some() {
|
||||
let runtime_secs = timer
|
||||
.end
|
||||
.unwrap()
|
||||
.saturating_duration_since(timer.start.unwrap())
|
||||
.as_secs_f32();
|
||||
summary.push_str(&format!("\n{0: <10} | {1: <10}s", timer.name, runtime_secs));
|
||||
}
|
||||
}
|
||||
summary
|
||||
|
||||
+2996
File diff suppressed because it is too large.
Load diff
+15
-40
@@ -1,6 +1,7 @@
|
||||
//! Provides a means to read, parse and hold configuration options for scans.
|
||||
use clap::{Parser, ValueEnum};
|
||||
use serde_derive::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -88,10 +89,6 @@ pub struct Opts {
|
||||
#[arg(short, long)]
|
||||
pub no_config: bool,
|
||||
|
||||
/// Hide the banner
|
||||
#[arg(long)]
|
||||
pub no_banner: bool,
|
||||
|
||||
/// Custom path to config file
|
||||
#[arg(short, long, value_parser)]
|
||||
pub config_path: Option<PathBuf>,
|
||||
@@ -113,7 +110,7 @@ pub struct Opts {
|
||||
/// it will do every port at the same time. Although, your OS may not
|
||||
/// support this.
|
||||
#[arg(short, long, default_value = "4500")]
|
||||
pub batch_size: usize,
|
||||
pub batch_size: u16,
|
||||
|
||||
/// The timeout in milliseconds before a port is assumed to be closed.
|
||||
#[arg(short, long, default_value = "1500")]
|
||||
@@ -126,7 +123,7 @@ pub struct Opts {
|
||||
|
||||
/// Automatically ups the ULIMIT with the value you provided.
|
||||
#[arg(short, long)]
|
||||
pub ulimit: Option<usize>,
|
||||
pub ulimit: Option<u64>,
|
||||
|
||||
/// The order of scanning to be performed. The "serial" option will
|
||||
/// scan ports in ascending order while the "random" option will scan
|
||||
@@ -154,10 +151,6 @@ pub struct Opts {
|
||||
#[arg(short, long, value_delimiter = ',')]
|
||||
pub exclude_ports: Option<Vec<u16>>,
|
||||
|
||||
/// A list of comma separated CIDRs, IPs, or hosts to be excluded from scanning.
|
||||
#[arg(short = 'x', long = "exclude-addresses", value_delimiter = ',')]
|
||||
pub exclude_addresses: Option<Vec<String>>,
|
||||
|
||||
/// UDP scanning mode, finds UDP ports that send back responses
|
||||
#[arg(long)]
|
||||
pub udp: bool,
|
||||
@@ -200,7 +193,7 @@ impl Opts {
|
||||
|
||||
merge_required!(
|
||||
addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts,
|
||||
command, udp, no_banner
|
||||
command, udp
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,10 +210,14 @@ impl Opts {
|
||||
|
||||
// Only use top ports when the user asks for them
|
||||
if self.top && config.ports.is_some() {
|
||||
self.ports = config.ports.clone();
|
||||
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.as_ref().unwrap().len());
|
||||
for entry in config.ports.as_ref().unwrap().keys() {
|
||||
ports.push(entry.parse().unwrap());
|
||||
}
|
||||
self.ports = Some(ports);
|
||||
}
|
||||
|
||||
merge_optional!(range, resolver, ulimit, exclude_ports, exclude_addresses);
|
||||
merge_optional!(range, resolver, ulimit, exclude_ports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,12 +237,10 @@ impl Default for Opts {
|
||||
resolver: None,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: true,
|
||||
no_banner: false,
|
||||
top: false,
|
||||
scripts: ScriptsRequired::Default,
|
||||
config_path: None,
|
||||
exclude_ports: None,
|
||||
exclude_addresses: None,
|
||||
udp: false,
|
||||
}
|
||||
}
|
||||
@@ -258,22 +253,20 @@ impl Default for Opts {
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
addresses: Option<Vec<String>>,
|
||||
ports: Option<Vec<u16>>,
|
||||
ports: Option<HashMap<String, u16>>,
|
||||
range: Option<PortRange>,
|
||||
greppable: Option<bool>,
|
||||
accessible: Option<bool>,
|
||||
batch_size: Option<usize>,
|
||||
batch_size: Option<u16>,
|
||||
timeout: Option<u32>,
|
||||
tries: Option<u8>,
|
||||
ulimit: Option<usize>,
|
||||
ulimit: Option<u64>,
|
||||
resolver: Option<String>,
|
||||
scan_order: Option<ScanOrder>,
|
||||
command: Option<Vec<String>>,
|
||||
scripts: Option<ScriptsRequired>,
|
||||
exclude_ports: Option<Vec<u16>>,
|
||||
exclude_addresses: Option<Vec<String>>,
|
||||
udp: Option<bool>,
|
||||
no_banner: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
@@ -288,20 +281,13 @@ impl Config {
|
||||
/// addresses = ["127.0.0.1", "127.0.0.1"]
|
||||
/// ports = [80, 443, 8080]
|
||||
/// greppable = true
|
||||
/// scan_order = "Serial"
|
||||
/// scan_order: "Serial"
|
||||
/// exclude_ports = [8080, 9090, 80]
|
||||
/// udp = false
|
||||
///
|
||||
pub fn read(custom_config_path: Option<PathBuf>) -> Self {
|
||||
let mut content = String::new();
|
||||
let config_path = custom_config_path.unwrap_or_else(|| {
|
||||
let path = default_config_path();
|
||||
match path.exists() {
|
||||
true => path,
|
||||
false => old_default_config_path(),
|
||||
}
|
||||
});
|
||||
|
||||
let config_path = custom_config_path.unwrap_or_else(default_config_path);
|
||||
if config_path.exists() {
|
||||
content = match fs::read_to_string(config_path) {
|
||||
Ok(content) => content,
|
||||
@@ -323,15 +309,6 @@ impl Config {
|
||||
|
||||
/// Constructs default path to config toml
|
||||
pub fn default_config_path() -> PathBuf {
|
||||
let Some(mut config_path) = dirs::config_dir() else {
|
||||
panic!("Could not infer config file path.");
|
||||
};
|
||||
config_path.push(".rustscan.toml");
|
||||
config_path
|
||||
}
|
||||
|
||||
/// Returns the deprecated home directory config path used for backwards compatibility.
|
||||
pub fn old_default_config_path() -> PathBuf {
|
||||
let Some(mut config_path) = dirs::home_dir() else {
|
||||
panic!("Could not infer config file path.");
|
||||
};
|
||||
@@ -363,9 +340,7 @@ mod tests {
|
||||
scan_order: Some(ScanOrder::Random),
|
||||
scripts: None,
|
||||
exclude_ports: None,
|
||||
exclude_addresses: None,
|
||||
udp: Some(false),
|
||||
no_banner: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -21,17 +21,17 @@
|
||||
//! start: 1,
|
||||
//! end: 1_000,
|
||||
//! };
|
||||
//! let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random); // can be serial, random or manual https://github.com/RustScan/RustScan/blob/master/src/port_strategy/mod.rs
|
||||
//! let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
//! let scanner = Scanner::new(
|
||||
//! &addrs, // the addresses to scan
|
||||
//! 10, // batch_size is how many ports at a time should be scanned
|
||||
//! Duration::from_millis(100), //T imeout is the time RustScan should wait before declaring a port closed. As datatype Duration.
|
||||
//! 1, // Tries, how many retries should RustScan do?
|
||||
//! true, // greppable is whether or not RustScan should print things, or wait until the end to print only the ip
|
||||
//! strategy, // the port strategy used
|
||||
//! true, // accessible, should the output be A11Y compliant?
|
||||
//! vec![9000], // What ports should RustScan exclude?
|
||||
//! false, // is this a UDP scan?
|
||||
//! &addrs,
|
||||
//! 10,
|
||||
//! Duration::from_millis(100),
|
||||
//! 1,
|
||||
//! true,
|
||||
//! strategy,
|
||||
//! true,
|
||||
//! vec![9000],
|
||||
//! false,
|
||||
//! );
|
||||
//!
|
||||
//! let scan_result = block_on(scanner.run());
|
||||
|
||||
+61
-72
@@ -23,9 +23,9 @@ extern crate dirs;
|
||||
|
||||
// Average value for Ubuntu
|
||||
#[cfg(unix)]
|
||||
const DEFAULT_FILE_DESCRIPTORS_LIMIT: usize = 8000;
|
||||
const DEFAULT_FILE_DESCRIPTORS_LIMIT: u64 = 8000;
|
||||
// Safest batch size based on experimentation
|
||||
const AVERAGE_BATCH_SIZE: usize = 3000;
|
||||
const AVERAGE_BATCH_SIZE: u16 = 3000;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -46,7 +46,7 @@ fn main() {
|
||||
let config = Config::read(opts.config_path.clone());
|
||||
opts.merge(&config);
|
||||
|
||||
debug!("Main() `opts` arguments are {opts:?}");
|
||||
debug!("Main() `opts` arguments are {:?}", opts);
|
||||
|
||||
let scripts_to_run: Vec<ScriptFile> = match init_scripts(&opts.scripts) {
|
||||
Ok(scripts_to_run) => scripts_to_run,
|
||||
@@ -60,9 +60,9 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Scripts initialized {scripts_to_run:?}");
|
||||
debug!("Scripts initialized {:?}", &scripts_to_run);
|
||||
|
||||
if !opts.greppable && !opts.accessible && !opts.no_banner {
|
||||
if !opts.greppable && !opts.accessible {
|
||||
print_opening(&opts);
|
||||
}
|
||||
|
||||
@@ -78,11 +78,16 @@ fn main() {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let batch_size: usize = infer_batch_size(&opts, adjust_ulimit_size(&opts));
|
||||
let batch_size: u16 = infer_batch_size(&opts, adjust_ulimit_size(&opts));
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let batch_size: usize = AVERAGE_BATCH_SIZE;
|
||||
let batch_size: u16 = AVERAGE_BATCH_SIZE;
|
||||
|
||||
// Added by wasuaje - 01/26/2024:
|
||||
// exclude_ports is an exclusion port list
|
||||
//
|
||||
// Added by brendanglancy - 5/19/2024:
|
||||
// udp is an option to do a udp scan
|
||||
let scanner = Scanner::new(
|
||||
&ips,
|
||||
batch_size,
|
||||
@@ -94,7 +99,7 @@ fn main() {
|
||||
opts.exclude_ports.unwrap_or_default(),
|
||||
opts.udp,
|
||||
);
|
||||
debug!("Scanner finished building: {scanner:?}");
|
||||
debug!("Scanner finished building: {:?}", scanner);
|
||||
|
||||
let mut portscan_bench = NamedTimer::start("Portscan");
|
||||
let scan_result = block_on(scanner.run());
|
||||
@@ -136,7 +141,7 @@ fn main() {
|
||||
|
||||
// if option scripts is none, no script will be spawned
|
||||
if opts.greppable || opts.scripts == ScriptsRequired::None {
|
||||
println!("{ip} -> [{ports_str}]");
|
||||
println!("{} -> [{}]", &ip, ports_str);
|
||||
continue;
|
||||
}
|
||||
detail!("Starting Script(s)", opts.greppable, opts.accessible);
|
||||
@@ -146,7 +151,7 @@ fn main() {
|
||||
// This part allows us to add commandline arguments to the Script call_format, appending them to the end of the command.
|
||||
if !opts.command.is_empty() {
|
||||
let user_extra_args = &opts.command.join(" ");
|
||||
debug!("Extra args vec {user_extra_args:?}");
|
||||
debug!("Extra args vec {:?}", user_extra_args);
|
||||
if script_f.call_format.is_some() {
|
||||
let mut call_f = script_f.call_format.unwrap();
|
||||
call_f.push(' ');
|
||||
@@ -156,7 +161,7 @@ fn main() {
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
debug!("Call format {call_f}");
|
||||
debug!("Call format {}", call_f);
|
||||
script_f.call_format = Some(call_f);
|
||||
}
|
||||
}
|
||||
@@ -173,7 +178,7 @@ fn main() {
|
||||
);
|
||||
match script.run() {
|
||||
Ok(script_result) => {
|
||||
detail!(script_result.clone(), opts.greppable, opts.accessible);
|
||||
detail!(script_result.to_string(), opts.greppable, opts.accessible);
|
||||
}
|
||||
Err(e) => {
|
||||
warning!(&format!("Error {e}"), opts.greppable, opts.accessible);
|
||||
@@ -187,7 +192,7 @@ fn main() {
|
||||
benchmarks.push(script_bench);
|
||||
rustscan_bench.end();
|
||||
benchmarks.push(rustscan_bench);
|
||||
debug!("Benchmarks raw {benchmarks:?}");
|
||||
debug!("Benchmarks raw {:?}", benchmarks);
|
||||
info!("{}", benchmarks.summary());
|
||||
}
|
||||
|
||||
@@ -195,17 +200,22 @@ fn main() {
|
||||
#[allow(clippy::items_after_statements, clippy::needless_raw_string_hashes)]
|
||||
fn print_opening(opts: &Opts) {
|
||||
debug!("Printing opening");
|
||||
let s = r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-.
|
||||
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
|
||||
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
|
||||
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
|
||||
The Modern Day Port Scanner."#;
|
||||
|
||||
let s = format!(
|
||||
"{}\n{}\n{}\n{}\n{}",
|
||||
r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-."#,
|
||||
r#"| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |"#,
|
||||
r#"| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |"#,
|
||||
r#"`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'"#,
|
||||
r#"The Modern Day Port Scanner."#
|
||||
);
|
||||
println!("{}", s.gradient(Color::Green).bold());
|
||||
let info = r#"________________________________________
|
||||
: http://discord.skerritt.blog :
|
||||
: https://github.com/RustScan/RustScan :
|
||||
--------------------------------------"#;
|
||||
let info = format!(
|
||||
"{}\n{}\n{}\n{}",
|
||||
r#"________________________________________"#,
|
||||
r#": http://discord.skerritt.blog :"#,
|
||||
r#": https://github.com/RustScan/RustScan :"#,
|
||||
r#" --------------------------------------"#
|
||||
);
|
||||
println!("{}", info.gradient(Color::Yellow).bold());
|
||||
funny_opening!();
|
||||
|
||||
@@ -219,26 +229,13 @@ The Modern Day Port Scanner."#;
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
|
||||
if opts.config_path.is_none() {
|
||||
let old_config_path = input::old_default_config_path();
|
||||
detail!(
|
||||
format!(
|
||||
"For backwards compatibility, the config file may also be at {old_config_path:?}"
|
||||
),
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn adjust_ulimit_size(opts: &Opts) -> usize {
|
||||
fn adjust_ulimit_size(opts: &Opts) -> u64 {
|
||||
use rlimit::Resource;
|
||||
use std::convert::TryInto;
|
||||
|
||||
if let Some(limit) = opts.ulimit {
|
||||
let limit = limit as u64;
|
||||
if Resource::NOFILE.set(limit, limit).is_ok() {
|
||||
detail!(
|
||||
format!("Automatically increasing ulimit value to {limit}."),
|
||||
@@ -255,12 +252,14 @@ fn adjust_ulimit_size(opts: &Opts) -> usize {
|
||||
}
|
||||
|
||||
let (soft, _) = Resource::NOFILE.get().unwrap();
|
||||
soft.try_into().unwrap_or(usize::MAX)
|
||||
soft
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
|
||||
let mut batch_size = opts.batch_size;
|
||||
fn infer_batch_size(opts: &Opts, ulimit: u64) -> u16 {
|
||||
use std::convert::TryInto;
|
||||
|
||||
let mut batch_size: u64 = opts.batch_size.into();
|
||||
|
||||
// Adjust the batch size when the ulimit value is lower than the desired batch size
|
||||
if ulimit < batch_size {
|
||||
@@ -271,7 +270,7 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
|
||||
// When the OS supports high file limits like 8000, but the user
|
||||
// selected a batch size higher than this we should reduce it to
|
||||
// a lower number.
|
||||
if ulimit < AVERAGE_BATCH_SIZE {
|
||||
if ulimit < AVERAGE_BATCH_SIZE.into() {
|
||||
// ulimit is smaller than aveage batch size
|
||||
// user must have very small ulimit
|
||||
// decrease batch size to half of ulimit
|
||||
@@ -280,7 +279,7 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
|
||||
batch_size = ulimit / 2;
|
||||
} else if ulimit > DEFAULT_FILE_DESCRIPTORS_LIMIT {
|
||||
info!("Batch size is now average batch size");
|
||||
batch_size = AVERAGE_BATCH_SIZE;
|
||||
batch_size = AVERAGE_BATCH_SIZE.into();
|
||||
} else {
|
||||
batch_size = ulimit - 100;
|
||||
}
|
||||
@@ -293,6 +292,8 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
|
||||
}
|
||||
|
||||
batch_size
|
||||
.try_into()
|
||||
.expect("Couldn't fit the batch size into a u16.")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -304,10 +305,8 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn batch_size_lowered() {
|
||||
let opts = Opts {
|
||||
batch_size: 50_000,
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 120);
|
||||
|
||||
assert!(batch_size < opts.batch_size);
|
||||
@@ -316,61 +315,51 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn batch_size_lowered_average_size() {
|
||||
let opts = Opts {
|
||||
batch_size: 50_000,
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 9_000);
|
||||
|
||||
assert_eq!(batch_size, 3_000);
|
||||
assert!(batch_size == 3_000);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn batch_size_equals_ulimit_lowered() {
|
||||
// because ulimit and batch size are same size, batch size is lowered
|
||||
// to ULIMIT - 100
|
||||
let opts = Opts {
|
||||
batch_size: 50_000,
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 5_000);
|
||||
|
||||
assert_eq!(batch_size, 4_900);
|
||||
assert!(batch_size == 4_900);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn batch_size_adjusted_2000() {
|
||||
// ulimit == batch_size
|
||||
let opts = Opts {
|
||||
batch_size: 50_000,
|
||||
ulimit: Some(2_000),
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
opts.ulimit = Some(2_000);
|
||||
let batch_size = adjust_ulimit_size(&opts);
|
||||
|
||||
assert_eq!(batch_size, 2_000);
|
||||
assert!(batch_size == 2_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_high_ulimit_no_greppable_mode() {
|
||||
let opts = Opts {
|
||||
batch_size: 10,
|
||||
greppable: false,
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 10;
|
||||
opts.greppable = false;
|
||||
|
||||
let batch_size = infer_batch_size(&opts, 1_000_000);
|
||||
|
||||
assert_eq!(batch_size, opts.batch_size);
|
||||
assert!(batch_size == opts.batch_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_print_opening_no_panic() {
|
||||
let opts = Opts {
|
||||
ulimit: Some(2_000),
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.ulimit = Some(2_000);
|
||||
// print opening should not panic
|
||||
print_opening(&opts);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Provides a means to hold configuration options specifically for port scanning.
|
||||
mod range_iterator;
|
||||
use crate::input::{PortRange, ScanOrder};
|
||||
use rand::rng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use range_iterator::RangeIterator;
|
||||
|
||||
/// Represents options of port scanning.
|
||||
@@ -35,7 +35,7 @@ impl PortStrategy {
|
||||
}
|
||||
ScanOrder::Serial => PortStrategy::Manual(ports.unwrap()),
|
||||
ScanOrder::Random => {
|
||||
let mut rng = rng();
|
||||
let mut rng = thread_rng();
|
||||
let mut ports = ports.unwrap();
|
||||
ports.shuffle(&mut rng);
|
||||
PortStrategy::Manual(ports)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use gcd::Gcd;
|
||||
use rand::RngExt;
|
||||
use rand::Rng;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct RangeIterator {
|
||||
@@ -27,8 +27,8 @@ impl RangeIterator {
|
||||
|
||||
// Randomly choose a number within the range to be the first
|
||||
// and assign it as a pick.
|
||||
let mut rng = rand::rng();
|
||||
let normalized_first_pick = rng.random_range(0..normalized_end);
|
||||
let mut rng = rand::thread_rng();
|
||||
let normalized_first_pick = rng.gen_range(0..normalized_end);
|
||||
|
||||
Self {
|
||||
active: true,
|
||||
@@ -83,14 +83,14 @@ fn pick_random_coprime(end: u32) -> u32 {
|
||||
let range_boundary = end / 4;
|
||||
let lower_range = range_boundary;
|
||||
let upper_range = end - range_boundary;
|
||||
let mut rng = rand::rng();
|
||||
let mut candidate = rng.random_range(lower_range..upper_range);
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut candidate = rng.gen_range(lower_range..upper_range);
|
||||
|
||||
for _ in 0..10 {
|
||||
if end.gcd(candidate) == 1 {
|
||||
return candidate;
|
||||
}
|
||||
candidate = rng.random_range(lower_range..upper_range);
|
||||
candidate = rng.gen_range(lower_range..upper_range);
|
||||
}
|
||||
|
||||
end - 1
|
||||
|
||||
+19
-14
@@ -25,11 +25,13 @@ use std::{
|
||||
/// batch_size is how many ports at a time should be scanned
|
||||
/// Timeout is the time RustScan should wait before declaring a port closed. As datatype Duration.
|
||||
/// greppable is whether or not RustScan should print things, or wait until the end to print only the ip and open ports.
|
||||
/// Added by wasuaje - 01/26/2024:
|
||||
/// exclude_ports is an exclusion port list
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[derive(Debug)]
|
||||
pub struct Scanner {
|
||||
ips: Vec<IpAddr>,
|
||||
batch_size: usize,
|
||||
batch_size: u16,
|
||||
timeout: Duration,
|
||||
tries: NonZeroU8,
|
||||
greppable: bool,
|
||||
@@ -44,7 +46,7 @@ pub struct Scanner {
|
||||
impl Scanner {
|
||||
pub fn new(
|
||||
ips: &[IpAddr],
|
||||
batch_size: usize,
|
||||
batch_size: u16,
|
||||
timeout: Duration,
|
||||
tries: u8,
|
||||
greppable: bool,
|
||||
@@ -69,6 +71,8 @@ impl Scanner {
|
||||
/// Runs scan_range with chunk sizes
|
||||
/// If you want to run RustScan normally, this is the entry point used
|
||||
/// Returns all open ports as `Vec<u16>`
|
||||
/// Added by wasuaje - 01/26/2024:
|
||||
/// Filtering port against exclude port list
|
||||
pub async fn run(&self) -> Vec<SocketAddr> {
|
||||
let ports: Vec<u16> = self
|
||||
.port_strategy
|
||||
@@ -94,7 +98,7 @@ impl Scanner {
|
||||
debug!("Start scanning sockets. \nBatch size {}\nNumber of ip-s {}\nNumber of ports {}\nTargets all together {} ",
|
||||
self.batch_size,
|
||||
self.ips.len(),
|
||||
ports.len(),
|
||||
&ports.len(),
|
||||
(self.ips.len() * ports.len()));
|
||||
|
||||
while let Some(result) = ftrs.next().await {
|
||||
@@ -112,8 +116,8 @@ impl Scanner {
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("Typical socket connection errors {errors:?}");
|
||||
debug!("Open Sockets found: {:?}", open_sockets);
|
||||
debug!("Typical socket connection errors {:?}", errors);
|
||||
debug!("Open Sockets found: {:?}", &open_sockets);
|
||||
open_sockets
|
||||
}
|
||||
|
||||
@@ -144,13 +148,16 @@ impl Scanner {
|
||||
for nr_try in 1..=tries {
|
||||
match self.connect(socket).await {
|
||||
Ok(tcp_stream) => {
|
||||
debug!("Connection was successful, shutting down stream {}", socket);
|
||||
debug!(
|
||||
"Connection was successful, shutting down stream {}",
|
||||
&socket
|
||||
);
|
||||
if let Err(e) = tcp_stream.shutdown(Shutdown::Both) {
|
||||
debug!("Shutdown stream error {}", e);
|
||||
debug!("Shutdown stream error {}", &e);
|
||||
}
|
||||
self.fmt_ports(socket);
|
||||
|
||||
debug!("Return Ok after {nr_try} tries");
|
||||
debug!("Return Ok after {} tries", nr_try);
|
||||
return Ok(socket);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -161,7 +168,7 @@ impl Scanner {
|
||||
if nr_try == tries {
|
||||
error_string.push(' ');
|
||||
error_string.push_str(&socket.ip().to_string());
|
||||
return Err(io::Error::other(error_string));
|
||||
return Err(io::Error::new(io::ErrorKind::Other, error_string));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -190,9 +197,7 @@ impl Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
Err(io::Error::other(format!(
|
||||
"UDP scan timed-out for all tries on socket {socket}"
|
||||
)))
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
/// Performs the connection to the socket with timeout
|
||||
@@ -271,7 +276,7 @@ impl Scanner {
|
||||
|
||||
match io::timeout(wait, udp_socket.recv(&mut buf)).await {
|
||||
Ok(size) => {
|
||||
debug!("Received {size} bytes");
|
||||
debug!("Received {} bytes", size);
|
||||
self.fmt_ports(socket);
|
||||
Ok(true)
|
||||
}
|
||||
@@ -285,7 +290,7 @@ impl Scanner {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Err E binding sock {e:?}");
|
||||
println!("Err E binding sock {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<'s> SocketIterator<'s> {
|
||||
}
|
||||
|
||||
#[allow(clippy::doc_link_with_quotes)]
|
||||
impl Iterator for SocketIterator<'_> {
|
||||
impl<'s> Iterator for SocketIterator<'s> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
/// Returns a socket based on the combination of one of the provided
|
||||
|
||||
+25
-74
@@ -108,20 +108,16 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
|
||||
scripts_to_run.push(default_script);
|
||||
}
|
||||
ScriptsRequired::Custom => {
|
||||
let script_config = ScriptConfig::read_config()?;
|
||||
debug!("Script config \n{script_config:?}");
|
||||
|
||||
let script_dir_base = if let Some(config_directory) = &script_config.directory {
|
||||
PathBuf::from(config_directory)
|
||||
} else {
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("Could not infer scripts path."))?
|
||||
};
|
||||
|
||||
let script_paths = find_scripts(script_dir_base)?;
|
||||
debug!("Scripts paths \n{script_paths:?}");
|
||||
let scripts_dir_base =
|
||||
dirs::home_dir().ok_or_else(|| anyhow!("Could not infer scripts path."))?;
|
||||
let script_paths = find_scripts(scripts_dir_base)?;
|
||||
debug!("Scripts paths \n{:?}", script_paths);
|
||||
|
||||
let parsed_scripts = parse_scripts(script_paths);
|
||||
debug!("Scripts parsed \n{parsed_scripts:?}");
|
||||
debug!("Scripts parsed \n{:?}", parsed_scripts);
|
||||
|
||||
let script_config = ScriptConfig::read_config()?;
|
||||
debug!("Script config \n{:?}", script_config);
|
||||
|
||||
// Only Scripts that contain all the tags found in ScriptConfig will be selected.
|
||||
if let Some(config_hashset) = script_config.tags {
|
||||
@@ -135,14 +131,14 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
|
||||
} else {
|
||||
debug!(
|
||||
"\nScript tags does not match config tags {:?} {}",
|
||||
script_hashset,
|
||||
&script_hashset,
|
||||
script.path.unwrap().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("\nScript(s) to run {scripts_to_run:?}");
|
||||
debug!("\nScript(s) to run {:?}", scripts_to_run);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +148,7 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
|
||||
pub fn parse_scripts(scripts: Vec<PathBuf>) -> Vec<ScriptFile> {
|
||||
let mut parsed_scripts: Vec<ScriptFile> = Vec::with_capacity(scripts.len());
|
||||
for script in scripts {
|
||||
debug!("Parsing script {}", script.display());
|
||||
debug!("Parsing script {}", &script.display());
|
||||
if let Some(script_file) = ScriptFile::new(script) {
|
||||
parsed_scripts.push(script_file);
|
||||
}
|
||||
@@ -224,7 +220,7 @@ impl Script {
|
||||
// Some variables get changed before read, and compiler throws warning on warn(unused_assignments)
|
||||
#[allow(unused_assignments)]
|
||||
pub fn run(self) -> Result<String> {
|
||||
debug!("run self {:?}", self);
|
||||
debug!("run self {:?}", &self);
|
||||
|
||||
let separator = self.ports_separator.unwrap_or_else(|| ",".into());
|
||||
|
||||
@@ -269,14 +265,14 @@ impl Script {
|
||||
};
|
||||
to_run = default_template.fill_with_struct(&exec_parts)?;
|
||||
}
|
||||
debug!("\nScript format to run {to_run}");
|
||||
debug!("\nScript format to run {}", to_run);
|
||||
execute_script(&to_run)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn execute_script(script: &str) -> Result<String> {
|
||||
debug!("\nScript arguments {script}");
|
||||
debug!("\nScript arguments {}", script);
|
||||
|
||||
let (cmd, arg) = if cfg!(unix) {
|
||||
("sh", "-c")
|
||||
@@ -285,7 +281,7 @@ fn execute_script(script: &str) -> Result<String> {
|
||||
};
|
||||
|
||||
match Command::new(cmd)
|
||||
.args([arg, script])
|
||||
.args(&[arg, script])
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
@@ -314,15 +310,16 @@ fn execute_script(script: &str) -> Result<String> {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
}
|
||||
Err(error) => {
|
||||
debug!("Command error {error}",);
|
||||
debug!("Command error {}", error.to_string());
|
||||
Err(anyhow!(error.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_scripts(path: PathBuf) -> Result<Vec<PathBuf>> {
|
||||
pub fn find_scripts(mut path: PathBuf) -> Result<Vec<PathBuf>> {
|
||||
path.push(".rustscan_scripts");
|
||||
if path.is_dir() {
|
||||
debug!("Scripts folder found {}", path.display());
|
||||
debug!("Scripts folder found {}", &path.display());
|
||||
let mut files_vec: Vec<PathBuf> = Vec::new();
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
@@ -360,20 +357,20 @@ impl ScriptFile {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Failed to read file: {}", real_path.display());
|
||||
debug!("Failed to read file: {}", &real_path.display());
|
||||
return None;
|
||||
}
|
||||
debug!("ScriptFile {} lines\n{}", real_path.display(), lines_buf);
|
||||
debug!("ScriptFile {} lines\n{}", &real_path.display(), &lines_buf);
|
||||
|
||||
match toml::from_str::<ScriptFile>(&lines_buf) {
|
||||
Ok(mut parsed) => {
|
||||
debug!("Parsed ScriptFile{} \n{:?}", real_path.display(), parsed);
|
||||
debug!("Parsed ScriptFile{} \n{:?}", &real_path.display(), &parsed);
|
||||
parsed.path = Some(real_path);
|
||||
// parsed_scripts.push(parsed);
|
||||
Some(parsed)
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to parse ScriptFile headers {e}");
|
||||
debug!("Failed to parse ScriptFile headers {}", e.to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -385,7 +382,6 @@ pub struct ScriptConfig {
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub ports: Option<Vec<String>>,
|
||||
pub developer: Option<Vec<String>>,
|
||||
pub directory: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
@@ -404,7 +400,7 @@ impl ScriptConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{find_scripts, parse_scripts, Script, ScriptFile};
|
||||
|
||||
// Function for testing only, it inserts static values into ip and open_ports
|
||||
// Doesn't use impl in case it's implemented in the super module at some point
|
||||
@@ -422,7 +418,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn find_and_parse_scripts() {
|
||||
let scripts = find_scripts("fixtures/.rustscan_scripts".into()).unwrap();
|
||||
let scripts = find_scripts("fixtures/".into()).unwrap();
|
||||
let scripts = parse_scripts(scripts);
|
||||
assert_eq!(scripts.len(), 4);
|
||||
}
|
||||
@@ -519,49 +515,4 @@ mod tests {
|
||||
// output has a newline at the end by default, .trim() trims it
|
||||
assert_eq!(output.trim(), "Total args passed to fixtures/.rustscan_scripts/test_script.pl : 2\nArg # 1 : 127.0.0.1\nArg # 2 : 80,8080");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_directory_config() {
|
||||
// Create test config
|
||||
let config_str = r#"
|
||||
tags = ["core_approved", "example"]
|
||||
directory = "fixtures/.rustscan_scripts"
|
||||
"#;
|
||||
|
||||
let config: ScriptConfig = toml::from_str(config_str).unwrap();
|
||||
assert_eq!(
|
||||
config.directory,
|
||||
Some("fixtures/.rustscan_scripts".to_string())
|
||||
);
|
||||
|
||||
// Test that the directory is actually used
|
||||
let script_dir_base = PathBuf::from(config.directory.unwrap());
|
||||
let scripts = find_scripts(script_dir_base).unwrap();
|
||||
|
||||
// Verify we found the test script
|
||||
assert!(scripts.iter().any(|p| p
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.map(|s| s == "test_script.txt")
|
||||
.unwrap_or(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_directory_fallback() {
|
||||
let config_str = r#"
|
||||
tags = ["core_approved", "example"]
|
||||
"#;
|
||||
|
||||
let config: ScriptConfig = toml::from_str(config_str).unwrap();
|
||||
assert_eq!(config.directory, None);
|
||||
|
||||
// Test fallback to home directory
|
||||
let script_dir_base = if let Some(config_directory) = &config.directory {
|
||||
PathBuf::from(config_directory)
|
||||
} else {
|
||||
dirs::home_dir().unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(script_dir_base, dirs::home_dir().unwrap());
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -43,7 +43,7 @@ macro_rules! output {
|
||||
($name:expr) => {
|
||||
println!(
|
||||
"{} {}",
|
||||
ansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
|
||||
RGansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
|
||||
$name
|
||||
);
|
||||
};
|
||||
@@ -68,7 +68,7 @@ macro_rules! output {
|
||||
macro_rules! funny_opening {
|
||||
// prints a funny quote / opening
|
||||
() => {
|
||||
use rand::seq::IndexedRandom;
|
||||
use rand::seq::SliceRandom;
|
||||
let quotes = vec![
|
||||
"Nmap? More like slowmap.🐢",
|
||||
"🌍HACK THE PLANET🌍",
|
||||
@@ -97,9 +97,8 @@ macro_rules! funny_opening {
|
||||
"RustScan: Exploring the digital landscape, one IP at a time.",
|
||||
"TreadStone was here 🚀",
|
||||
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
|
||||
"Scanning ports so fast, even the internet got a speeding ticket!",
|
||||
];
|
||||
let random_quote = quotes.choose(&mut rand::rng()).unwrap();
|
||||
let random_quote = quotes.choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
println!("{}\n", random_quote);
|
||||
};
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ fn run_rustscan_with_timeout(args: &[&str], timeout: Duration) {
|
||||
let end = Instant::now();
|
||||
let duration = end.saturating_duration_since(start).as_secs_f32();
|
||||
|
||||
println!("time: {duration:1.1}s");
|
||||
println!("time: {:1.1}s", duration);
|
||||
}
|
||||
|
||||
mod timelimits {
|
||||
|
||||
Reference in new issue
Block a user