Compare commits

..
Author SHA1 Message Date
bee-san a6e9a8281d Fix cargo clippy warnings 2024-09-13 18:47:43 +01:00
30 changed files with 4100 additions and 2087 deletions

No files matched your search

-4
View File
@@ -7,10 +7,6 @@ assignees: ''
---
IMPORTANT: If you do not fill this out, your issue will be automatically closed.
As of November 2024, RustScan no longer offiically supports any packages other than `cargo`. If you have an issue with Docker or a `.deb`, please bring it up with whoever created it :)
**Describe the bug**
A clear and concise description of what the bug is.
+82 -165
View File
@@ -1,179 +1,96 @@
name: CD Pipeline
on: [push]
name: Build
on:
pull_request:
paths-ignore:
- '.github/**'
push:
branches:
- master
paths-ignore:
- '.github/**'
schedule:
- cron: '00 01 * * *'
jobs:
build-nix:
env:
IN_PIPELINE: true
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/master'
strategy:
matrix:
type: [ubuntu-x64, ubuntu-x86, armv7, aarch64]
include:
- type: ubuntu-x64
os: ubuntu-latest
target: x86_64-unknown-linux-musl
name: x86_64-linux-rustscan
path: target/x86_64-unknown-linux-musl/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
- type: ubuntu-x86
os: ubuntu-latest
target: i686-unknown-linux-musl
name: x86-linux-rustscan
path: target/i686-unknown-linux-musl/release/rustscan
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
- type: armv7
os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
name: armv7-linux-rustscan
path: target/armv7-unknown-linux-gnueabihf/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
- type: aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
name: aarch64-linux-rustscan
path: target/aarch64-unknown-linux-gnu/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
steps:
- uses: actions/checkout@v7
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
with:
command: build
target: ${{ matrix.target }}
args: "--locked --release"
strip: true
toolchain: stable
- name: Build tar.gz for homebrew installs
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
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
- uses: actions/upload-artifact@v7
if: matrix.type == 'ubuntu-x64'
with:
name: ${{ matrix.name }}.tar.gz
path: ${{ matrix.name }}.tar.gz
build-deb:
needs: [build-nix]
check:
name: Check
runs-on: ubuntu-latest
env:
IN_PIPELINE: true
steps:
- uses: actions/checkout@v7
- name: Install cargo-deb
run: cargo install -f cargo-deb
- uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: musl-tools # provides musl-gcc
version: 1.0
- name: Install musl toolchain
run: rustup target add x86_64-unknown-linux-musl
- name: Deb Build
run: cargo deb --target=x86_64-unknown-linux-musl
- name: Upload Deb Artifact
uses: actions/upload-artifact@v7
with:
name: rustscan.deb
path: ./target/x86_64-unknown-linux-musl/debian/*
- name: Checkout sources
uses: actions/checkout@v4
build-macos:
env:
IN_PIPELINE: true
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v7
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
command: build
target: x86_64-apple-darwin
args: "--locked --release"
strip: true
profile: minimal
toolchain: stable
- 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
with:
name: x86_64-macos-rustscan
path: target/x86_64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v7
with:
name: x86_64-macos-rustscan.tar.gz
path: x86_64-macos-rustscan.tar.gz
build-macos-aarch64:
env:
IN_PIPELINE: true
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v7
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
with:
command: build
target: aarch64-apple-darwin
args: "--locked --release"
strip: true
toolchain: stable
- 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
with:
name: aarch64-macos-rustscan
path: target/aarch64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v7
with:
name: aarch64-macos-rustscan.tar.gz
path: aarch64-macos-rustscan.tar.gz
override: true
build-windows:
env:
IN_PIPELINE: true
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/master'
- uses: Swatinem/rust-cache@v2
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
test:
name: Test Suite
strategy:
matrix:
type: [windows-x64, windows-x86]
include:
- type: windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
name: x86_64-windows-rustscan.exe
path: target\x86_64-pc-windows-msvc\release\rustscan.exe
- type: windows-x86
os: windows-latest
target: i686-pc-windows-msvc
name: x86-windows-rustscan.exe
path: target\i686-pc-windows-msvc\release\rustscan.exe
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
command: build
target: ${{ matrix.target }}
args: "--locked --release"
strip: true
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- uses: Swatinem/rust-cache@v2
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
submodules: true
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
- uses: actions/upload-artifact@v7
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Run rustdoc lints
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --all-features --no-deps --document-private-items
-14
View File
@@ -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 }}
+38
View File
@@ -0,0 +1,38 @@
name: Release - Distribute Docker image
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
# release:
# types: [published]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
password: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: rustscan/rustscan
flavor: latest=true
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ contains(fromJson('["push", "release"]'), github.event_name) }} # Publish to docker registry only on push event or new release.
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+21
View File
@@ -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'
+216
View File
@@ -0,0 +1,216 @@
name: Release - Distribute binaries
on:
# If we want to make release using github interface.
# release:
# types: [published]
# If we want to make release by pushing new tag.
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
pull_request:
branches:
- master
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/release.yml'
env:
BIN_NAME: rustscan
PROJECT_NAME: rustscan
REPO_NAME: RustScan/RustScan
BREW_TAP: RustScan/homebrew-tap
jobs:
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # don't fail other jobs if one fails
matrix:
# aarch64-linux is failing due to python3 missing issues during tests. I'm removing it for now.
# build: [x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos, x86_64-windows, x86_64-win-gnu]
build: [x86_64-linux, x86_64-macos, aarch64-macos, x86_64-windows, x86_64-win-gnu]
include:
- build: aarch64-macos
os: macos-latest
rust: stable
target: aarch64-apple-darwin
cross: true
# - build: aarch64-linux
# os: ubuntu-20.04
# rust: stable
# target: aarch64-unknown-linux-gnu
# cross: true
- build: x86_64-linux
os: ubuntu-20.04
rust: stable
target: x86_64-unknown-linux-gnu
cross: false
- build: x86_64-macos
os: macos-latest
rust: stable
target: x86_64-apple-darwin
cross: false
- build: x86_64-windows
os: windows-2019
rust: stable
target: x86_64-pc-windows-msvc
cross: false
- build: x86_64-win-gnu
os: windows-2019
rust: stable-x86_64-gnu
target: x86_64-pc-windows-gnu
cross: false
steps:
- name: Set Git config (windows only)
if: contains(matrix.os, 'windows') # Windows is missing some dependencies
run: |
git config --global pack.windowMemory "100m"
git config --global pack.packSizeLimit "100m"
git config --global pack.threads "1"
git config --global pack.deltaCacheSize "512m"
- name: Install dependencies
if: contains(matrix.build, 'aarch64-linux') # Windows is missing some dependencies
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu python3
- name: Install python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Checkout sources
uses: actions/checkout@v4
with:
submodules: true
- name: Install ${{ matrix.rust }} toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.cross }}
command: test
args: --release --locked --target ${{ matrix.target }}
- name: Build release binary
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.cross }}
command: build
args: --release --locked --target ${{ matrix.target }}
- name: Calculate tag name
# if: contains(matrix.build, 'linux')
shell: bash
run: |
name=dev
if [[ ${GITHUB_REF} =~ refs/tags/[0-9]+.[0-9]+.[0-9]+ ]]; then
name=${GITHUB_REF#refs/tags/}
fi
echo "TAG=$name" >> $GITHUB_ENV
id: tagname
- name: Build and package artifacts
shell: bash
run: |
mkdir dist
if [[ "${{ matrix.build }}" =~ "windows" ]]; then
cp "target/${{ matrix.target }}/release/$BIN_NAME.exe" "dist/"
else
cp "target/${{ matrix.target }}/release/$BIN_NAME" "dist/"
fi
if [[ "${{ matrix.build }}" =~ "linux" ]]; then
cargo install cargo-deb
cargo deb --target ${{ matrix.target }} --deb-version ${TAG}
cp "target/${{ matrix.target }}/debian/rustscan_${TAG}_amd64.deb" "dist/" || true
fi
- uses: actions/[email protected]
with:
name: bins-${{ matrix.build }}
path: dist
package:
name: Package
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
submodules: false
- uses: actions/download-artifact@v4
with:
path: dist
- name: Calculate tag name
# if: contains(matrix.build, 'linux')
shell: bash
run: |
name=dev
if [[ ${GITHUB_REF} =~ refs/tags/[0-9]+.[0-9]+.[0-9]+ ]]; then
name=${GITHUB_REF#refs/tags/}
fi
echo "TAG=$name" >> $GITHUB_ENV
id: tagname
- name: Build archive
shell: bash
run: |
set -ex
rm -rf tmp
mkdir tmp
for dir in dist/bins-* ; do
platform=${dir#"dist/bins-"}
unset exe
if [[ $platform =~ "win" ]]; then
exe=".exe"
fi
pkgname=$PROJECT_NAME-$TAG-$platform
mkdir tmp/$pkgname
cp $dir/$BIN_NAME$exe dist/ || true
mv $dir/$BIN_NAME$exe tmp/$pkgname
chmod +x tmp/$pkgname/$BIN_NAME$exe
if [[ $platform =~ "linux" ]]; then
mv "$dir/rustscan_${TAG}_amd64.deb" dist/ || true
fi
tar cJf dist/$pkgname.tar.xz -C tmp $pkgname
7z a dist/$pkgname.zip tmp/$pkgname
done
- name: Upload binaries to release
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist/*
file_glob: true
tag: ${{ github.ref }}
overwrite: true
# - uses: mislav/bump-homebrew-formula-action@v3
# with:
# formula-path: ${{env.PROJECT_NAME}}.rb
# homebrew-tap: ${{ env.BREW_TAP }}
# download-url: 'https://github.com/${{ env.REPO_NAME }}/releases/download/${{ github.ref }}/${{env.PROJECT_NAME}}-${{ github.ref }}-x86_64-macos.tar.xz'
# commit-message: updating formula for ${{ env.PROJECT_NAME }}
# env:
# COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
- run: cargo publish --token ${CRATES_TOKEN}
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release'
env:
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
-34
View File
@@ -1,34 +0,0 @@
name: Test
on:
pull_request:
branches: [master]
push:
branches:
- master
jobs:
test:
name: Test Suite
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v7
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Install Just
uses: extractions/setup-just@v4
- name: Run just
run: just test
-2
View File
@@ -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
+475 -1219
View File
File diff suppressed because it is too large. Load diff
+17 -21
View File
@@ -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.9", features = ["derive", "wrap_help"] }
colored = "2.1.0"
async-std = "1.7.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.1"
log = "0.4.22"
env_logger = "0.11.3"
anstream = "=0.6.14"
dirs = "5.0.1"
gcd = "2.0.1"
rand = "0.10.1"
colorful = "0.3.2"
rand = "0.8.5"
colorful = "0.2.1"
ansi_term = "0.12.1"
toml = "1.1.2"
toml = "0.8.14"
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"
subprocess = "0.2.6"
text_placeholder = { version = "0.5", features = ["struct_context"] }
once_cell = "1.21.4"
once_cell = "1.19.0"
[dev-dependencies]
parameterized = "2.0.0"
wait-timeout = "0.2"
criterion = { version = "0.8", features = ["html_reports"] }
[package.metadata.deb]
depends = "$auto, nmap"
@@ -61,7 +61,3 @@ path = "src/main.rs"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] }
[[bench]]
name = "benchmark_portscan"
harness = false
+25
View File
@@ -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.2 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" ]
+30 -30
View File
@@ -9,6 +9,16 @@
![Arch Linux package][badge-1] ![Built with Rust][badge-2] ![GitHub All Releases][badge-3] ![Crates.io][badge-4] ![Discord][badge-5] ![Actions][badge-6]
---
| 🐋 `(Recommended)` | 👩‍💻 | 🏗️ | 🔧 |
| :---------------------------------------------------------: | :---------------------------: | :-------------------------: | :-------------------------: |
| [![Docker][DockerPic]][Docker] | [![Kali][Kali1]][Kali/Debian] | [![Arch][Arch]][Arch-Linux] | [![apple][apple]][Homebrew] |
| `docker pull rustscan/rustscan:latest`</br>[Docker][usage-0] | [Link to Documentation][kali] | `yay -S rustscan` | `brew install rustscan` |
---
</div>
# 🤔 What is this?
@@ -17,32 +27,6 @@
The Modern Port Scanner. **Find ports quickly (3 seconds at its fastest)**. Run scripts through our scripting engine (Python, Lua, Shell supported).
# 🛠️ 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:
[![Packaging status](https://repology.org/badge/vertical-allrepos/rustscan.svg)](https://repology.org/project/rustscan/versions)
RustScan only officially supports Cargo installations, if you want to use that please install Rust and then `cargo install rustscan`
Example installations include:
MacOS:
```
brew install rustscan
```
Arch:
```
pacman -S rustscan
```
# ✨ Features
- Scans all 65k ports in **3 seconds**.
@@ -55,7 +39,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
@@ -63,6 +47,7 @@ Arch:
- 🐋 [Docker Usage][toc-docker-usage]
- 🦜 [Discord][discord]
- 🤸 [Usage][usage-1]
- 🎪 [Community][toc-community]
# 🔭 Why RustScan?
@@ -120,6 +105,21 @@ RustScan is one of the first penetration testing tools that aims to be entirely
RustScan has continuous integration testing that aims to ensure it is accessible, and we are constantly working on ways to improve our accessibility and ensure _everyone_ can use RustScan.
# 📖 Full Installation Guide
You can find our guide [here][install-1].
## 🦊 Community Distributions
Here are all of RustScan's community distributions.
If you maintain a community distribution and want it listed here, leave an issue / pull request / Discord message or however, you want to let us know.
- [Open Suse][distributions-1]
- [Fedora/CentOS][distributions-2]
[![Packaging status][rustscan-svg]][repology-1]
# 🤸 Usage
We have 2 usage guides. [Basic Usage][usage-1] and [Things you may want to do][usage-2].
@@ -145,7 +145,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://skerritt.blog"><img src="https://avatars3.githubusercontent.com/u/10378052?v=4" width="100px;" alt=""/><br /><sub><b>Bee</b></sub></a><br /><a href="#infra-beeskerritt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/RustScan/RustScan/commits?author=beeskerritt" title="Tests">⚠️</a> <a href="https://github.com/RustScan/RustScan/commits?author=beesan" title="Code">💻</a> <a href="#design-beeskerritt" title="Design">🎨</a></td>
<td align="center"><a href="https://skerritt.blog"><img src="https://avatars3.githubusercontent.com/u/10378052?v=4" width="100px;" alt=""/><br /><sub><b>Brandon</b></sub></a><br /><a href="#infra-brandonskerritt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/RustScan/RustScan/commits?author=brandonskerritt" title="Tests">⚠️</a> <a href="https://github.com/RustScan/RustScan/commits?author=brandonskerritt" title="Code">💻</a> <a href="#design-brandonskerritt" title="Design">🎨</a></td>
<td align="center"><a href="https://sakiir.ovh"><img src="https://avatars1.githubusercontent.com/u/9950578?v=4" width="100px;" alt=""/><br /><sub><b>SakiiR</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=SakiiR" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3ASakiiR" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/smackhack"><img src="https://avatars2.githubusercontent.com/u/48143394?v=4" width="100px;" alt=""/><br /><sub><b>smackhack</b></sub></a><br /><a href="#ideas-smackhack" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-smackhack" title="Examples">💡</a></td>
<td align="center"><a href="http://bernardoamc.github.io/"><img src="https://avatars0.githubusercontent.com/u/428984?v=4" width="100px;" alt=""/><br /><sub><b>Bernardo Araujo</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=bernardoamc" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3Abernardoamc" title="Bug reports">🐛</a> <a href="#design-bernardoamc" title="Design">🎨</a></td>
@@ -209,7 +209,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 +232,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/Continuous%20integration/badge.svg?branch=master
-106
View File
@@ -1,106 +0,0 @@
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 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();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
+2 -2
View File
@@ -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 -3
View File
@@ -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
-4
View File
@@ -1,4 +0,0 @@
tags = ["core_approved", "example"]
ports = ["80", "443", "8080"]
developer = ["example", "https://example.org"]
directory = "fixtures/.rustscan_scripts"
-18
View File
@@ -1,18 +0,0 @@
test:
cargo test
cargo clippy -- --deny warnings
cargo clippy --tests -- --deny warnings
cargo fmt --check
cargo doc --workspace --all-features --no-deps --document-private-items
fix:
cargo fmt
cargo clippy --fix
# release only runs if test runs
release: test
git checkout master
git pull
cargo generate-lockfile
cargo publish
git push
+33 -186
View File
@@ -1,12 +1,11 @@
//! Provides functions to parse input IP addresses, CIDRs or files.
use std::collections::BTreeSet;
use std::fs::{self, File};
use std::io::{prelude::*, BufReader};
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,
@@ -28,8 +27,6 @@ use crate::warning;
///
/// let ips = parse_addresses(&opts);
/// ```
///
/// Finally, any duplicates are removed to avoid excessive scans.
pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
let mut ips: Vec<IpAddr> = Vec::new();
let mut unresolved_addresses: Vec<&str> = Vec::new();
@@ -69,12 +66,6 @@ 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
}
@@ -92,21 +83,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 +110,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 +187,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 +203,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 +214,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,87 +223,55 @@ 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()
};
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(), 2_048);
}
#[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()
};
let ips = parse_addresses(&opts);
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();
+7 -5
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+16 -41
View File
@@ -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,27 +253,25 @@ 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))]
#[allow(clippy::doc_link_with_quotes)]
#[allow(clippy::manual_unwrap_or_default)]
#[allow(clippy::unwrap_or_default)]
impl Config {
/// Reads the configuration file with TOML format and parses it into a
/// Config struct.
@@ -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
View File
@@ -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 -75
View File
@@ -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;
@@ -35,9 +35,6 @@ extern crate log;
/// Faster Nmap scanning with Rust
/// If you're looking for the actual scanning, check out the module Scanner
fn main() {
#[cfg(not(unix))]
let _ = ansi_term::enable_ansi_support();
env_logger::init();
let mut benchmarks = Benchmark::init();
let mut rustscan_bench = NamedTimer::start("RustScan");
@@ -46,7 +43,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 +57,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 +75,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 +96,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 +138,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 +148,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 +158,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 +175,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 +189,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 +197,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 +226,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 +249,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 +267,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 +276,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 +289,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 +302,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 +312,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);
}
+2 -2
View File
@@ -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)
+6 -6
View File
@@ -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
+20 -15
View File
@@ -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
@@ -218,7 +223,7 @@ impl Scanner {
Ok(stream)
}
/// Binds to a UDP socket so we can send and receive packets
/// Binds to a UDP socket so we can send and recieve packets
/// # Example
///
/// ```compile_fail
@@ -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)
}
}
+1 -1
View File
@@ -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
+37 -119
View File
@@ -79,21 +79,19 @@ use crate::input::ScriptsRequired;
use anyhow::{anyhow, Result};
use log::debug;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fs::{self, File};
use std::io::{self, prelude::*};
use std::net::IpAddr;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::string::ToString;
use subprocess::{Exec, ExitStatus};
use text_placeholder::Template;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
static DEFAULT: &str = r#"tags = ["core_approved", "RustScan", "default"]
developer = [ "RustScan", "https://github.com/RustScan" ]
ports_separator = ","
call_format = "nmap -vvv -p {{port}} -{{ipversion}} {{ip}}"
call_format = "nmap -vvv -p {{port}} {{ip}}"
"#;
#[cfg(not(tarpaulin_include))]
@@ -108,20 +106,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 +129,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 +146,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);
}
@@ -190,14 +184,12 @@ struct ExecPartsScript {
script: String,
ip: String,
port: String,
ipversion: String,
}
#[derive(Serialize)]
struct ExecParts {
ip: String,
port: String,
ipversion: String,
}
impl Script {
@@ -224,7 +216,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());
@@ -252,77 +244,49 @@ impl Script {
script: self.path.unwrap().to_str().unwrap().to_string(),
ip: self.ip.to_string(),
port: ports_str,
ipversion: match &self.ip {
IpAddr::V4(_) => String::from("4"),
IpAddr::V6(_) => String::from("6"),
},
};
to_run = default_template.fill_with_struct(&exec_parts_script)?;
} else {
let exec_parts: ExecParts = ExecParts {
ip: self.ip.to_string(),
port: ports_str,
ipversion: match &self.ip {
IpAddr::V4(_) => String::from("4"),
IpAddr::V6(_) => String::from("6"),
},
};
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}");
let (cmd, arg) = if cfg!(unix) {
("sh", "-c")
} else {
("cmd.exe", "/c")
};
match Command::new(cmd)
.args([arg, script])
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.output()
{
Ok(output) => {
let status = output.status;
let es = match status.code() {
Some(code) => code,
_ => {
#[cfg(unix)]
{
status.signal().unwrap()
}
#[cfg(windows)]
{
return Err(anyhow!("Unknown exit status"));
}
}
debug!("\nScript arguments {}", script);
let process = Exec::shell(script);
match process.capture() {
Ok(c) => {
let es = match c.exit_status {
ExitStatus::Exited(c) => c.try_into().unwrap(),
ExitStatus::Signaled(c) => c.into(),
ExitStatus::Other(c) => c,
ExitStatus::Undetermined => -1,
};
if es != 0 {
return Err(anyhow!("Exit code = {}", es));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
Ok(c.stdout_str())
}
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 +324,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 +349,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 +367,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 +385,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 +482,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
View File
@@ -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
View File
@@ -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 {