Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae32e45512 | ||
|
|
8045af8cd2 | ||
|
|
221cf8694f | ||
|
|
bd32f4ead4 | ||
|
|
8625f939bf | ||
|
|
8f608eaf99 | ||
|
|
f9f2bf6cf8 | ||
|
|
6fb1196244 | ||
|
|
61c43dd5d0 | ||
|
|
075025113d | ||
|
|
e3645b5c81 | ||
|
|
a444ab66e1 | ||
|
|
30856350d3 | ||
|
|
7bff40a526 | ||
|
|
a38c6aa13b | ||
|
|
9b2c58fee5 | ||
|
|
198225a8da | ||
|
|
9a9dff422e | ||
|
|
227703a57e | ||
|
|
90bbed2a17 | ||
|
|
311cafcdbc | ||
|
|
02f1f6c7d1 | ||
|
|
2182a1bd01 | ||
|
|
0a42331dab | ||
|
|
a5e5797f9e | ||
|
|
9f2a752fb4 | ||
|
|
57de97bdc4 | ||
|
|
846afbdd5a | ||
|
|
52bf521a5d | ||
|
|
bef54d1496 | ||
|
|
76b3116b42 | ||
|
|
73665ed16e | ||
|
|
21b92a1d1b | ||
|
|
f9a7586857 | ||
|
|
6c05775394 | ||
|
|
043be7d6af | ||
|
|
5526657d84 | ||
|
|
859eb0e83a | ||
|
|
172ad63553 | ||
|
|
ed41af2c80 | ||
|
|
072d7335fd | ||
|
|
8a11532a25 | ||
|
|
f057a2113a | ||
|
|
da6c95f8f1 | ||
|
|
ea5c120fa7 | ||
|
|
cd86aaf30d | ||
|
|
d09bfeb37a | ||
|
|
4614e6bfe9 | ||
|
|
03877d376c | ||
|
|
da3f34da79 | ||
|
|
872eeb6e9e | ||
|
|
9bbee9d3ed | ||
|
|
1debe0035d | ||
|
|
cf314b4c63 | ||
|
|
4dcd929ba4 | ||
|
|
5b35a09b0b | ||
|
|
734595da46 | ||
|
|
943980ee83 | ||
|
|
61b691eaa4 | ||
|
|
6dcad646fa | ||
|
|
d2c84f5f33 | ||
|
|
0eaf63683e | ||
|
|
06c1d37aea | ||
|
|
4d560f4eb5 | ||
|
|
dbc3d510d5 | ||
|
|
68e1c8e8b6 | ||
|
|
15dd31e350 | ||
|
|
b3a64271dc | ||
|
|
abb08958cb | ||
|
|
d5935c4ce7 | ||
|
|
5c613082fb | ||
|
|
2868e13db4 | ||
|
|
31ed2e8914 | ||
|
|
0426aa83e3 | ||
|
|
4deb5eff53 | ||
|
|
81ad00dc29 | ||
|
|
64cac6e0fa | ||
|
|
739e9716fc | ||
|
|
6bf70f0f9a | ||
|
|
57dfb15852 | ||
|
|
282065752c | ||
|
|
982ae0d926 | ||
|
|
53f8a15e16 | ||
|
|
fc214a78b4 | ||
|
|
0ae289c25f | ||
|
|
9326fc4a0f | ||
|
|
c05ebaf857 | ||
|
|
6f8df7e77e | ||
|
|
488853f40e | ||
|
|
57c1e33650 | ||
|
|
699b578ade | ||
|
|
ebe5a326a8 | ||
|
|
abb7a3872c | ||
|
|
f74cb456b1 | ||
|
|
492d2fb3b3 | ||
|
|
af53591ccb | ||
|
|
6405b43f5a | ||
|
|
9a71253697 | ||
|
|
4d6d57fe2c | ||
|
|
a040ab5521 | ||
|
|
a546aa2ac0 | ||
|
|
868e61bb2e | ||
|
|
c4f8ceb336 | ||
|
|
9d80fb4fa7 | ||
|
|
e659d51976 | ||
|
|
64f4aecacd | ||
|
|
afe2a9452f | ||
|
|
4a6e74d507 | ||
|
|
eb905f7e7a | ||
|
|
561e7fb2f0 | ||
|
|
e5d22a0421 | ||
|
|
33c9388586 | ||
|
|
4dfe76737b | ||
|
|
cc0b344232 | ||
|
|
abaf100da9 | ||
|
|
43602793e0 | ||
|
|
7b827991c1 | ||
|
|
f4555ecb98 | ||
|
|
2fb8a399a7 | ||
|
|
e612ddc4e6 | ||
|
|
8bf75846d3 | ||
|
|
bf1cb3a5d0 | ||
|
|
01975d7238 | ||
|
|
8c4cc77e6b | ||
|
|
b0c1ab3177 | ||
|
|
6812cc3051 | ||
|
|
2800adec34 | ||
|
|
cf1aa67a21 | ||
|
|
c9708671e8 | ||
|
|
461f9aaa6d | ||
|
|
01a74b72df | ||
|
|
d301cf3b20 | ||
|
|
5e8d45bf01 | ||
|
|
c8a8915682 | ||
|
|
d786e24186 | ||
|
|
5b536621e6 | ||
|
|
c05f40ae70 | ||
|
|
24b9f8d8d3 |
No files matched your search
@@ -2,7 +2,7 @@ pkgbase = rustscan-bin
|
||||
pkgdesc = Faster Nmap Scanning with Rust
|
||||
pkgver = 1.1.0
|
||||
pkgrel = 1
|
||||
url = https://github.com/brandonskerritt/rustscan
|
||||
url = https://github.com/rustscan/rustscan
|
||||
arch = x86_64
|
||||
license = MIT
|
||||
depends = rustup
|
||||
|
||||
@@ -162,7 +162,9 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/47574893?v=4",
|
||||
"profile": "https://github.com/niklasmohrin",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -182,6 +184,81 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bergabman",
|
||||
"name": "bergabman",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/44554109?v=4",
|
||||
"profile": "https://github.com/bergabman",
|
||||
"contributions": [
|
||||
"code",
|
||||
"bug",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dmitris",
|
||||
"name": "Dmitry Savintsev",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/31205?v=4",
|
||||
"profile": "https://github.com/dmitris",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bofh69",
|
||||
"name": "Sebastian Andersson",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/1444315?v=4",
|
||||
"profile": "https://github.com/bofh69",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mattcorbin",
|
||||
"name": "Matt Corbin",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/6537765?v=4",
|
||||
"profile": "https://github.com/mattcorbin",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rootsploit",
|
||||
"name": "RootSploit",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/67270834?v=4",
|
||||
"profile": "http://rootsploit.com",
|
||||
"contributions": [
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "eiffel-fl",
|
||||
"name": "eiffel-fl",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/12171754?v=4",
|
||||
"profile": "https://github.com/eiffel-fl",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "u5surf",
|
||||
"name": "Y.Horie",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/14180225?v=4",
|
||||
"profile": "https://github.com/u5surf",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "okrplay",
|
||||
"name": "Oskar",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/32576280?v=4",
|
||||
"profile": "https://github.com/okrplay",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
github: bee-san
|
||||
patreon: brandonskerritt
|
||||
ko_fi: hacker
|
||||
custom: ["https://monzo.me/bee", "https://paypal.me/brandonskerritt"]
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
@@ -46,10 +46,3 @@ jobs:
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
links:
|
||||
name: markdown-link-check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
@@ -43,3 +43,80 @@ pkg/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rust,archlinuxpackages
|
||||
*.deb
|
||||
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
.idea/*
|
||||
.idea
|
||||
.idea*
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
script:
|
||||
- cargo build
|
||||
- cargo test
|
||||
- "ulimit -n 5000; cargo test timelimits:: -- --ignored --test-threads=1 --show-output"
|
||||
- cargo fmt -- --check
|
||||
after_failure:
|
||||
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rustscan"
|
||||
version = "1.8.0"
|
||||
authors = ["Bee <brando[email protected]>"]
|
||||
version = "2.1.1"
|
||||
authors = ["Autumn <autum[email protected]>"]
|
||||
edition = "2018"
|
||||
description = "Faster Nmap Scanning with Rust"
|
||||
homepage = "https://github.com/rustscan/rustscan"
|
||||
@@ -18,22 +18,30 @@ exclude = [
|
||||
|
||||
[dependencies]
|
||||
colored = "2.0.0"
|
||||
structopt = "0.3.17"
|
||||
async-std = "1.6.3"
|
||||
structopt = "0.3.20"
|
||||
async-std = "1.7.0"
|
||||
futures = "0.3"
|
||||
rlimit = "0.4.0"
|
||||
rlimit = "0.8.3"
|
||||
shell-words = "1.0.0"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.7.1"
|
||||
env_logger = "0.8.2"
|
||||
dirs = "3.0.1"
|
||||
gcd = "2.0.1"
|
||||
rand = "0.7.3"
|
||||
colorful = "0.2.1"
|
||||
ansi_term = "0.12.1"
|
||||
toml = "0.5.6"
|
||||
serde = "1.0.115"
|
||||
serde_derive = "1.0.115"
|
||||
cidr-utils = "0.5.0"
|
||||
toml = "0.5.7"
|
||||
serde = "1.0.124"
|
||||
serde_derive = "1.0.116"
|
||||
cidr-utils = "0.5.1"
|
||||
itertools = "0.9.0"
|
||||
trust-dns-resolver = { version = "0.21.1", features = ["dns-over-rustls"] }
|
||||
anyhow = "1.0.40"
|
||||
subprocess = "0.2.6"
|
||||
text_placeholder = { version = "0.4", features = ["struct_context"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wait-timeout = "0.2"
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto, nmap"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
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 /usr/src/rustscan
|
||||
COPY . .
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src/ src/
|
||||
RUN cargo install --path .
|
||||
|
||||
FROM alpine:3.12
|
||||
|
||||
@@ -10,12 +10,12 @@ build:
|
||||
build-linux:
|
||||
@echo 'Building for Linux... 🐧'
|
||||
cross build --release --target=x86_64-unknown-linux-musl
|
||||
mkdir -p target/release-archives && tar -czf target/release-archives/rustscan-linux.tar.gz target/x86_64-unknown-linux-musl/release/rustscan
|
||||
mkdir -p target/release-archives && tar -C target/x86_64-unknown-linux-musl/release -czf target/release-archives/rustscan-linux.tar.gz rustscan
|
||||
|
||||
build-mac:
|
||||
@echo 'Building for MacOS... 🍏'
|
||||
cross build --release --target=x86_64-apple-darwin
|
||||
mkdir -p target/release-archives && tar -czf target/release-archives/rustscan-mac.tar.gz target/x86_64-apple-darwin/release/rustscan
|
||||
mkdir -p target/release-archives && tar -C target/x86_64-apple-darwin/release -czf target/release-archives/rustscan-mac.tar.gz rustscan
|
||||
|
||||
shasum:
|
||||
shasum -a 256 target/release-archives/rustscan-*.tar.gz
|
||||
@@ -1,153 +0,0 @@
|
||||
<p align="center">
|
||||
➡️
|
||||
<a href="https://discord.gg/GFrQsGy">Discord</a> |
|
||||
<a href="https://github.com/RustScan/RustScan#-full-installation-guide">Installation Guide</a> |
|
||||
<a href="https://github.com/RustScan/RustScan#-usage">Usage Guide</a>
|
||||
⬅️
|
||||
<br>
|
||||
<img src="pictures/rustscan.png" height=400px width=400px>
|
||||
</p>
|
||||
<p align="center">
|
||||
<u><b> 👽 The Modern Day Port Scanner 👽
|
||||
</p>
|
||||
<p align="center">
|
||||
<img alt="AUR version" src="https://img.shields.io/aur/version/rustscan">
|
||||
<img src="https://img.shields.io/badge/Built%20with-Rust-Purple">
|
||||
<img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/rustscan/rustscan/total?label=GitHub%20Downloads">
|
||||
<img alt="Crates.io" src="https://img.shields.io/crates/d/rustscan?label=Cargo%20Downloads">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/736614461313515576">
|
||||
<img alt="Actions" src="https://github.com/RustScan/RustScan/workflows/Continuous%20integration/badge.svg?branch=master">
|
||||
</p>
|
||||
<hr>
|
||||
|
||||
| <p align="center"><a href="https://hub.docker.com/r/cmnatic/rustscan">🐋 Docker (Recommended) </a></p> | <p align="center"><a href="https://github.com/RustScan/RustScan/releases">👩💻 Kali / Debian </p> | <p align="center"><a href="https://aur.archlinux.org/packages/rustscan/">🏗️ Arch </a></p> | <p align="center"><a href="https://crates.io/crates/rustscan">🔧 Cargo (Universal) </a></p> |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/docker.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/kali.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/arch.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/rust.png?raw=true" /></p> |
|
||||
| `docker pull rustscan/rustscan:alpine` <p>[Usage](https://github.com/RustScan/RustScan#docker-whale) | [Read the install guide](https://github.com/Rustscan/RustScan/blob/master/README.md#%EF%B8%8F-debian--kali) | `yay -S rustscan` | `cargo install rustscan` |
|
||||
|
||||
<hr>
|
||||
|
||||
# 🤔 What is this?
|
||||
|
||||
RustScan is the port scanner for the modern world. Built with speed & you in mind.
|
||||
|
||||

|
||||
|
||||
|
||||
# ✨ Features
|
||||
|
||||
- Scans all 65k ports in **3 seconds** (max speed) **or as slow as you'd like**
|
||||
- Saves you time by automatically piping it into Nmap. No more manual copying and pasting!
|
||||
- Adaptive Learning System. The scanner learns how _you_ use it and improves itself over time.
|
||||
- Intuitive Scripting Engine support. Write scripts in Python!
|
||||
- IPv6, CIDR, top ports, multiple IP scanning, host resolution, and more!
|
||||
|
||||
## ‼️ Important Links
|
||||
|
||||
| Installation Guide | Documentation | Discord |
|
||||
| -------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------- |
|
||||
| 📖 [Installation Guide](https://github.com/RustScan/RustScan#-full-installation-guide) | 📚 [Documentation](https://rustscan.github.io/RustScan/) | 🦜 [Discord](https://discord.gg/GFrQsGy) |
|
||||
|
||||
|
||||
# 🔭 Why RustScan?
|
||||
|
||||
RustScan is a port scanner built for the modern day world. Initially created to aid in CTFs, but now expanded to real world usage.
|
||||
|
||||
One of RustScan's most popular features is the Nmap extension. Quickly scan ports with RustScan, run Nmap on the found ports.
|
||||
|
||||
**RustScan running in 8 seconds and finding all open ports out of 65k.**
|
||||
|
||||

|
||||
|
||||
# Packages
|
||||
|
||||
[](https://repology.org/project/rustscan/versions)
|
||||
|
||||
|
||||
# 🎪 Community
|
||||
|
||||
Howdy Space Cow-Person 🤠🌌
|
||||
|
||||
RustScan is always looking for contributors. Whether that's spelling mistakes or major changes, your help is **wanted** and welcomed here.
|
||||
|
||||
Before contributing, read our [code of conduct](https://github.com/RustScan/RustScan/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
TL;DR if you abuse members of our community you will be **perma-banned** 🤗
|
||||
|
||||
RustScan has 2 major labels for GitHub issues you should look at:
|
||||
|
||||
- Good First issue
|
||||
These are issues for newcomers to open source!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
- Help wanted
|
||||
These are issues that aren't really for newcomers, but we could still do wiht help!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22)
|
||||
|
||||
If you want to, solve the issue or comment on the issue for help.
|
||||
|
||||
The flow for contributing to open source software is:
|
||||
|
||||
- Fork the repo
|
||||
- Make changes
|
||||
- Pull request to the repo
|
||||
|
||||
And then comment on the issue that you've done.
|
||||
|
||||
RustScan also has some `// TODO`'s in the codebase, which are meant more for the core team but we wouldn't say no to help with these issues.
|
||||
|
||||
If you have any feature suggestions or bugs, leave a GitHub issue. We welcome any and all support :D
|
||||
|
||||
We communicate over Discord. [Click here](https://discord.gg/GFrQsGy) to join our Discord community!
|
||||
|
||||
## Rewarding you
|
||||
|
||||
I cannot pay you :-( But, I can place your GitHub profile on the README under `#Contributors` as a thank you! :)
|
||||
|
||||
Please read the [contributing.md file](contributing.md)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
|
||||
[](#contributors-)
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- 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>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>
|
||||
<td align="center"><a href="https://github.com/Isona"><img src="https://avatars2.githubusercontent.com/u/11759523?v=4" width="100px;" alt=""/><br /><sub><b>Izzy Whistlecroft</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/issues?q=author%3AIsona" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://imlonghao.com"><img src="https://avatars1.githubusercontent.com/u/4951333?v=4" width="100px;" alt=""/><br /><sub><b>imlonghao</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/issues?q=author%3Aimlonghao" title="Bug reports">🐛</a> <a href="#maintenance-imlonghao" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/royharoush"><img src="https://avatars3.githubusercontent.com/u/8113056?v=4" width="100px;" alt=""/><br /><sub><b>royharoush</b></sub></a><br /><a href="#ideas-royharoush" title="Ideas, Planning, & Feedback">🤔</a> <a href="#design-royharoush" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Atul9"><img src="https://avatars1.githubusercontent.com/u/3390330?v=4" width="100px;" alt=""/><br /><sub><b>Atul Bhosale</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=Atul9" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://tgotwig.me"><img src="https://avatars0.githubusercontent.com/u/30773779?v=4" width="100px;" alt=""/><br /><sub><b>Thomas Gotwig</b></sub></a><br /><a href="#platform-TGotwig" title="Packaging/porting to new platform">📦</a></td>
|
||||
<td align="center"><a href="https://github.com/remigourdon"><img src="https://avatars3.githubusercontent.com/u/2874133?v=4" width="100px;" alt=""/><br /><sub><b>Rémi Gourdon</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=remigourdon" title="Documentation">📖</a> <a href="https://github.com/RustScan/RustScan/commits?author=remigourdon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://cmnatic.co.uk"><img src="https://avatars3.githubusercontent.com/u/4163116?v=4" width="100px;" alt=""/><br /><sub><b>Ben (CMNatic)</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=cmnatic" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/commits?author=cmnatic" title="Documentation">📖</a> <a href="#design-cmnatic" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/Ferryistaken"><img src="https://avatars3.githubusercontent.com/u/47927670?v=4" width="100px;" alt=""/><br /><sub><b>Alessandro Ferrari</b></sub></a><br /><a href="#content-Ferryistaken" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://github.com/Phenomite"><img src="https://avatars2.githubusercontent.com/u/8285537?v=4" width="100px;" alt=""/><br /><sub><b>Phenomite</b></sub></a><br /><a href="#content-Phenomite" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4" width="100px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="#content-SuperSandro2000" title="Content">🖋</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3ASuperSandro2000" title="Bug reports">🐛</a> <a href="https://github.com/RustScan/RustScan/commits?author=SuperSandro2000" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://swag.lgbt"><img src="https://avatars2.githubusercontent.com/u/25358963?v=4" width="100px;" alt=""/><br /><sub><b>Cass</b></sub></a><br /><a href="#platform-caass" title="Packaging/porting to new platform">📦</a> <a href="https://github.com/RustScan/RustScan/commits?author=caass" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3Acaass" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://github.com/niklasmohrin"><img src="https://avatars0.githubusercontent.com/u/47574893?v=4" width="100px;" alt=""/><br /><sub><b>Niklas Mohrin</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=niklasmohrin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://liberapay.com/Artem4/"><img src="https://avatars0.githubusercontent.com/u/5614476?v=4" width="100px;" alt=""/><br /><sub><b>Artem Polishchuk</b></sub></a><br /><a href="#platform-tim77" title="Packaging/porting to new platform">📦</a></td>
|
||||
<td align="center"><a href="https://github.com/buermarc"><img src="https://avatars2.githubusercontent.com/u/44375277?v=4" width="100px;" alt=""/><br /><sub><b>buermarc</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=buermarc" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM rust
|
||||
# Install nmap first.
|
||||
RUN apt-get update -qy && apt-get install -qy nmap
|
||||
# Then install rustfmt and clippy for cargo.
|
||||
RUN rustup component add rustfmt clippy
|
||||
@@ -7,19 +7,21 @@ Before contributing, read our [code of conduct](https://github.com/RustScan/Rust
|
||||
TL;DR if you abuse members of our community you will be **perma-banned** with no chance to get unbanned. No warnings either. 🤗
|
||||
|
||||
RustScan has 2 major labels for GitHub issues you should look at:
|
||||
* Good First issue
|
||||
These are issues for newcomers to open source!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
* Help wanted
|
||||
These are issues that aren't really for newcomers, but we could still do wiht help!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22)
|
||||
|
||||
- Good First issue
|
||||
These are issues for newcomers to open source!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
- Help wanted
|
||||
These are issues that aren't really for newcomers, but we could still do with help!
|
||||
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22)
|
||||
|
||||
If you want to, solve the issue or comment on the issue for help.
|
||||
|
||||
The [flow for contributing](https://guides.github.com/introduction/flow/) to open source software is:
|
||||
* Fork the repo
|
||||
* Make changes
|
||||
* Pull request to the repo
|
||||
The flow for contributing to open source software is:
|
||||
|
||||
- Fork the repo
|
||||
- Make changes
|
||||
- Pull request to the repo
|
||||
|
||||
And then comment on the issue that you've done.
|
||||
|
||||
@@ -28,4 +30,40 @@ RustScan also has some `// TODO`'s in the codebase, which are meant more for the
|
||||
If you have any feature suggestions or bugs, leave a GitHub issue. We welcome any and all support :D
|
||||
|
||||
## Rewarding you
|
||||
|
||||
I cannot pay you :-( But, I can place your GitHub profile on the README under `#Contributors` as a thank you! :)
|
||||
|
||||
## Contributing development environment
|
||||
|
||||
To ease contribution to RustScan, you can use the `contributing.Dockerfile` to create a Docker image ready to build and play with RustScan.
|
||||
To build it you just need to run:
|
||||
|
||||
```bash
|
||||
you@home:~/RustScan$ docker build -t rustscan_contributing -f contributing.Dockerfile
|
||||
```
|
||||
|
||||
Then you need to run the container with a volume so it can access, _with read and write permissions_, to RustScan files:
|
||||
|
||||
```bash
|
||||
you@home:~/RustScan$ docker run -ti --rm -v "$PWD":/rustscan -w /rustscan rustscan_contributing bash
|
||||
```
|
||||
|
||||
You can now modify RustScan files with your favorite editor, once you want to compile and test your modifications, type the following in the container prompt:
|
||||
|
||||
```bash
|
||||
root@container:/rustscan# cargo build
|
||||
```
|
||||
|
||||
You are now ready to use RustScan:
|
||||
|
||||
```bash
|
||||
root@container:/rustscan# cargo run -- -b 2000 -t 5000 -a 127.0.0.1
|
||||
```
|
||||
|
||||
You can also format, lint with `clippy` and test the code with the following commands:
|
||||
|
||||
```bash
|
||||
root@container:/rustscan# cargo fmt
|
||||
root@container:/rustscan# cargo clippy
|
||||
root@container:/rustscan# cargo test
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/perl
|
||||
#tags = ["core_approved", "example",]
|
||||
#developer = [ "example", "https://example.org" ]
|
||||
#ports_separator = ","
|
||||
#call_format = "perl {{script}} {{ip}} {{port}}"
|
||||
|
||||
# Sriptfile parser stops at the first blank line with parsing.
|
||||
# This script will run itself as an argument with the system installed perl interpreter, ports will be concatenated with "," .
|
||||
# Unused field: trigger_port = "80"
|
||||
# get total arg passed to this script
|
||||
my $total = $#ARGV + 1;
|
||||
my $counter = 1;
|
||||
|
||||
# get script name
|
||||
my $scriptname = $0;
|
||||
|
||||
print "Total args passed to $scriptname : $total\n";
|
||||
|
||||
# Use loop to print all args stored in an array called @ARGV
|
||||
foreach my $a(@ARGV) {
|
||||
print "Arg # $counter : $a\n";
|
||||
$counter++;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/python3
|
||||
#tags = ["core_approved", "example",]
|
||||
#developer = [ "example", "https://example.org" ]
|
||||
#trigger_port = "80"
|
||||
#call_format = "python3 {{script}} {{ip}} {{port}}"
|
||||
|
||||
# Sriptfile parser stops at the first blank line with parsing.
|
||||
# This script will run itself as an argument with the system installed python interpreter, only scanning port 80.
|
||||
# Unused filed: ports_separator = ","
|
||||
|
||||
import sys
|
||||
|
||||
print('Python script ran with arguments', str(sys.argv))
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
#tags = ["core_approved", "example",]
|
||||
#developer = [ "example", "https://example.org" ]
|
||||
#ports_separator = ","
|
||||
#call_format = "bash {{script}} {{ip}} {{port}}"
|
||||
|
||||
# Sriptfile parser stops at the first blank line with parsing.
|
||||
# This script will run itself as an argument with the system installed bash interpreter, scanning all ports concatenated with "," .
|
||||
# Unused filed: trigger_port = "80"
|
||||
|
||||
# print all arguments passed to the script
|
||||
echo $@
|
||||
@@ -0,0 +1,9 @@
|
||||
#!intentional_blank_line
|
||||
#tags = ["core_approved", "example"]
|
||||
#developer = [ "example", "https://example.org" ]
|
||||
#ports_separator = ","
|
||||
#call_format = "nmap -vvv -p {{port}} {{ip}}"
|
||||
|
||||
# Scriptfile parser stops at the first blank line with parsing.
|
||||
# This script will run the system installed nmap, ports will be concatenated with "," .
|
||||
# Unused field: trigger_port = "80"
|
||||
@@ -0,0 +1,7 @@
|
||||
#!intentional_blank_line
|
||||
#tags = "core_approved"
|
||||
#developer = [ "example", "https://example.org" ]
|
||||
#ports_separator = ","
|
||||
#call_format = "nmap -vvv -p {{port}} {{ip}}"
|
||||
|
||||
# tags has to be an array, thus this file won't be parsed
|
||||
@@ -0,0 +1,6 @@
|
||||
127.0.0.1
|
||||
google.com
|
||||
example.com
|
||||
66666666666666.666666666666666666.66666666666666666.6666666666
|
||||
....
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.radiatorFixtures
|
||||
@@ -0,0 +1,42 @@
|
||||
# https://github.com/minimaxir/big-list-of-naughty-strings
|
||||
|
||||
|
||||
|
||||
|
||||
Ω≈ç√∫˜µ≤≥÷
|
||||
åß∂ƒ©˙∆˚¬…æ
|
||||
œ∑´®†¥¨ˆøπ“‘
|
||||
¡™£¢∞§¶•ªº–≠
|
||||
¸˛Ç◊ı˜Â¯˘¿
|
||||
ÅÍÎÏ˝ÓÔÒÚÆ☃
|
||||
Œ„´‰ˇÁ¨ˆØ∏”’
|
||||
`⁄€‹›fifl‡°·‚—±
|
||||
⅛⅜⅝⅞
|
||||
ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя
|
||||
٠١٢٣٤٥٦٧٨٩
|
||||
|
||||
|
||||
|
||||
⁰⁴⁵
|
||||
₀₁₂
|
||||
⁰⁴⁵₀₁₂
|
||||
ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็
|
||||
|
||||
田中さんにあげて下さい
|
||||
パーティーへ行かないか
|
||||
和製漢語
|
||||
部落格
|
||||
사회과학원 어학연구소
|
||||
찦차를 타고 온 펲시맨과 쑛다리 똠방각하
|
||||
社會科學院語學研究所
|
||||
울란바토르
|
||||
𠜎𠜱𠝹𠱓𠱸𠲖𠳏
|
||||
𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠
|
||||
𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌
|
||||
𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈
|
||||
𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰
|
||||
𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘
|
||||
𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐
|
||||
⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢
|
||||
|
||||
ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Test/Example ScriptConfig file
|
||||
|
||||
# Tags to filter on scripts. Only scripts containing all these tags will run.
|
||||
tags = ["core_approved", "example"]
|
||||
|
||||
# If it's present then only those scripts will run which has a tag ports = "80". Not yet implemented.
|
||||
#
|
||||
# ex.:
|
||||
# ports = ["80"]
|
||||
# ports = ["80","81","8080"]
|
||||
ports = ["80"]
|
||||
|
||||
# Only this developer(s) scripts to run. Not yet implemented.
|
||||
developer = ["example"]
|
||||
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1,131 @@
|
||||
# The configurations that used for the recording, feel free to edit them
|
||||
config:
|
||||
|
||||
# Specify a command to be executed
|
||||
# like `/bin/bash -l`, `ls`, or any other commands
|
||||
# the default is bash for Linux
|
||||
# or powershell.exe for Windows
|
||||
command: zsh
|
||||
|
||||
# Specify the current working directory path
|
||||
# the default is the current working directory path
|
||||
cwd: /home/bee/Documents/RustScan
|
||||
|
||||
# Export additional ENV variables
|
||||
env:
|
||||
recording: true
|
||||
|
||||
# Explicitly set the number of columns
|
||||
# or use `auto` to take the current
|
||||
# number of columns of your shell
|
||||
cols: 107
|
||||
|
||||
# Explicitly set the number of rows
|
||||
# or use `auto` to take the current
|
||||
# number of rows of your shell
|
||||
rows: 43
|
||||
|
||||
# Amount of times to repeat GIF
|
||||
# If value is -1, play once
|
||||
# If value is 0, loop indefinitely
|
||||
# If value is a positive number, loop n times
|
||||
repeat: 0
|
||||
|
||||
# Quality
|
||||
# 1 - 100
|
||||
quality: 100
|
||||
|
||||
# Delay between frames in ms
|
||||
# If the value is `auto` use the actual recording delays
|
||||
frameDelay: auto
|
||||
|
||||
# Maximum delay between frames in ms
|
||||
# Ignored if the `frameDelay` isn't set to `auto`
|
||||
# Set to `auto` to prevent limiting the max idle time
|
||||
maxIdleTime: 2000
|
||||
|
||||
# The surrounding frame box
|
||||
# The `type` can be null, window, floating, or solid`
|
||||
# To hide the title use the value null
|
||||
# Don't forget to add a backgroundColor style with a null as type
|
||||
frameBox:
|
||||
type: floating
|
||||
title: Terminalizer
|
||||
style:
|
||||
border: 0px black solid
|
||||
# boxShadow: none
|
||||
# margin: 0px
|
||||
|
||||
# Add a watermark image to the rendered gif
|
||||
# You need to specify an absolute path for
|
||||
# the image on your machine or a URL, and you can also
|
||||
# add your own CSS styles
|
||||
watermark:
|
||||
imagePath: null
|
||||
style:
|
||||
position: absolute
|
||||
right: 15px
|
||||
bottom: 15px
|
||||
width: 100px
|
||||
opacity: 0.9
|
||||
|
||||
# Cursor style can be one of
|
||||
# `block`, `underline`, or `bar`
|
||||
cursorStyle: block
|
||||
|
||||
# Font family
|
||||
# You can use any font that is installed on your machine
|
||||
# in CSS-like syntax
|
||||
fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace"
|
||||
|
||||
# The size of the font
|
||||
fontSize: 12
|
||||
|
||||
# The height of lines
|
||||
lineHeight: 1
|
||||
|
||||
# The spacing between letters
|
||||
letterSpacing: 0
|
||||
|
||||
# Theme
|
||||
theme:
|
||||
background: "transparent"
|
||||
foreground: "#afafaf"
|
||||
cursor: "#c7c7c7"
|
||||
black: "#232628"
|
||||
red: "#fc4384"
|
||||
green: "#b3e33b"
|
||||
yellow: "#ffa727"
|
||||
blue: "#75dff2"
|
||||
magenta: "#ae89fe"
|
||||
cyan: "#708387"
|
||||
white: "#d5d5d0"
|
||||
brightBlack: "#626566"
|
||||
brightRed: "#ff7fac"
|
||||
brightGreen: "#c8ed71"
|
||||
brightYellow: "#ebdf86"
|
||||
brightBlue: "#75dff2"
|
||||
brightMagenta: "#ae89fe"
|
||||
brightCyan: "#b1c6ca"
|
||||
brightWhite: "#f9f9f4"
|
||||
|
||||
# Records, feel free to edit them
|
||||
records:
|
||||
- delay: 436
|
||||
content: "\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]2;bee@beehive:~/Documents/RustScan\a\e]1;..ents/RustScan\a"
|
||||
- delay: 162
|
||||
content: "\r\e[0m\e[27m\e[24m\e[J\r\n\e[1m\e[0m\e[1m\e[36mRustScan\e[0m\e[36m\e[39m\e[1m \e[0m\e[1mon \e[0m\e[1m\e[37m\e[1m\e[37m\e[0m\e[37m\e[1m\e[37m\e[35m master\e[0m\e[35m\e[39m\e[1m\e[0m\e[1m\e[0m\e[1m\e[31m [?]\e[0m\e[31m\e[39m\e[1m\e[0m\e[0m\e[39m\e[1m \e[0m\e[1mis \e[0m\e[1m\e[31m\U0001F4E6 vrustscan:1.10.0\e[0m\e[31m\e[39m\e[1m \e[0m\e[1mvia \e[0m\e[1m\e[31m\U0001D5E5 v1.46.0\e[0m\e[31m\e[39m\e[1m \e[0m\r\n\e[1m\e[0m\e[1m\e[32m➜ \e[0m\e[32m\e[39m\e[1m\e[0m\e[K\e[?1h\e=\e[?2004h"
|
||||
- delay: 471
|
||||
content: "r\brustscan 127.0.0.1 --accessible --no-nmap --ulimit 5000\e[55D\e[7mr\e[7mu\e[7ms\e[7mt\e[7ms\e[7mc\e[7ma\e[7mn\e[7m \e[7m1\e[7m2\e[7m7\e[7m.\e[7m0\e[7m.\e[7m0\e[7m.\e[7m1\e[7m \e[7m-\e[7m-\e[7ma\e[7mc\e[7mc\e[7me\e[7ms\e[7ms\e[7mi\e[7mb\e[7ml\e[7me\e[7m \e[7m-\e[7m-\e[7mn\e[7mo\e[7m-\e[7mn\e[7mm\e[7ma\e[7mp\e[7m \e[7m-\e[7m-\e[7mu\e[7ml\e[7mi\e[7mm\e[7mi\e[7mt\e[7m \e[7m5\e[7m0\e[7m0\e[7m0\e[27m"
|
||||
- delay: 269
|
||||
content: "\e[55D\e[27mr\e[27mu\e[27ms\e[27mt\e[27ms\e[27mc\e[27ma\e[27mn\e[27m \e[27m1\e[27m2\e[27m7\e[27m.\e[27m0\e[27m.\e[27m0\e[27m.\e[27m1\e[27m \e[27m-\e[27m-\e[27ma\e[27mc\e[27mc\e[27me\e[27ms\e[27ms\e[27mi\e[27mb\e[27ml\e[27me\e[27m \e[27m-\e[27m-\e[27mn\e[27mo\e[27m-\e[27mn\e[27mm\e[27ma\e[27mp\e[27m \e[27m-\e[27m-\e[27mu\e[27ml\e[27mi\e[27mm\e[27mi\e[27mt\e[27m \e[27m5\e[27m0\e[27m0\e[27m0\e[?1l\e>\e[?2004l\r\r\n\e]2;rustscan 127.0.0.1 --accessible --no-nmap --ulimit 5000\a\e]1;rustscan\aAutomatically increasing ulimit value to 5000.\r\n"
|
||||
- delay: 1536
|
||||
content: "Open 127.0.0.1:38602\r\n"
|
||||
- delay: 395
|
||||
content: "Open 127.0.0.1:50078\r\n"
|
||||
- delay: 450
|
||||
content: "127.0.0.1 -> [38602,50078]\r\n\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]2;bee@beehive:~/Documents/RustScan\a\e]1;..ents/RustScan\a"
|
||||
- delay: 143
|
||||
content: "\r\e[0m\e[27m\e[24m\e[J\r\n\e[1m\e[0m\e[1m\e[36mRustScan\e[0m\e[36m\e[39m\e[1m \e[0m\e[1mon \e[0m\e[1m\e[37m\e[1m\e[37m\e[0m\e[37m\e[1m\e[37m\e[35m master\e[0m\e[35m\e[39m\e[1m\e[0m\e[1m\e[0m\e[1m\e[31m [?]\e[0m\e[31m\e[39m\e[1m\e[0m\e[0m\e[39m\e[1m \e[0m\e[1mis \e[0m\e[1m\e[31m\U0001F4E6 vrustscan:1.10.0\e[0m\e[31m\e[39m\e[1m \e[0m\e[1mvia \e[0m\e[1m\e[31m\U0001D5E5 v1.46.0\e[0m\e[31m\e[39m\e[1m \e[0m\e[1mtook \e[0m\e[1m\e[33m2s\e[0m\e[33m\e[39m\e[1m \e[0m\r\n\e[1m\e[0m\e[1m\e[32m➜ \e[0m\e[32m\e[39m\e[1m\e[0m\e[K\e[?1h\e=\e[?2004h"
|
||||
- delay: 563
|
||||
content: "\e[?2004l\r\r\n"
|
||||
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 258 KiB |
@@ -0,0 +1 @@
|
||||
cargo publish && cargo build --release && git add Cargo.lock && git commit -m 'Updating lock file' && gpsup
|
||||
@@ -0,0 +1,89 @@
|
||||
//! # Usage
|
||||
//! ```
|
||||
//! // Initiate Benchmark vector
|
||||
//! let mut bm = Benchmark::init();
|
||||
//! // Start named timer with name
|
||||
//! let mut example_bench = NamedTimer::start("Example Bench");
|
||||
//! // Stop named timer
|
||||
//! example_bench.stop();
|
||||
//! // Add named timer to Benchmarks
|
||||
//! bm.push(example_bench);
|
||||
//! // Print Benchmark Summary
|
||||
//! info!({}, bm.summary());
|
||||
//! ```
|
||||
use std::time::Instant;
|
||||
|
||||
/// A Benchmark struct to hold NamedTimers with name, start and end Instants,
|
||||
#[derive(Debug)]
|
||||
pub struct Benchmark {
|
||||
named_timers: Vec<NamedTimer>,
|
||||
}
|
||||
|
||||
impl Benchmark {
|
||||
pub fn init() -> Self {
|
||||
Self {
|
||||
named_timers: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, timer: NamedTimer) {
|
||||
self.named_timers.push(timer);
|
||||
}
|
||||
|
||||
/// Summary of the benchmarks will destruct the vector,
|
||||
/// formats every element the same way and return
|
||||
/// a single String with all the available information
|
||||
/// for easy printing
|
||||
pub fn summary(&self) -> String {
|
||||
let mut summary = String::from("\nRustScan Benchmark Summary");
|
||||
|
||||
for timer in &self.named_timers {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// The purpose of NamedTimer is to hold a name,
|
||||
/// start Instant and end Instant for a specific timer.
|
||||
/// The given name will be presented in the benchmark summary,
|
||||
/// start and end Instants will be used for calculating runtime.
|
||||
#[derive(Debug)]
|
||||
pub struct NamedTimer {
|
||||
name: &'static str,
|
||||
start: Option<Instant>,
|
||||
end: Option<Instant>,
|
||||
}
|
||||
|
||||
impl NamedTimer {
|
||||
pub fn start(name: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
start: Some(Instant::now()),
|
||||
end: None,
|
||||
}
|
||||
}
|
||||
pub fn end(&mut self) {
|
||||
self.end = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn benchmark() {
|
||||
let mut benchmarks = Benchmark::init();
|
||||
let mut test_timer = NamedTimer::start("test");
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
test_timer.end();
|
||||
benchmarks.push(test_timer);
|
||||
benchmarks.push(NamedTimer::start("only_start"));
|
||||
assert!(benchmarks
|
||||
.summary()
|
||||
.contains("\nRustScan Benchmark Summary\ntest | 0."));
|
||||
assert_ne!(benchmarks.summary().contains("only_start"), true);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use serde_derive::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
const LOWEST_PORT_NUMBER: u16 = 1;
|
||||
@@ -9,13 +11,26 @@ arg_enum! {
|
||||
/// Represents the strategy in which the port scanning will run.
|
||||
/// - Serial will run from start to end, for example 1 to 1_000.
|
||||
/// - Random will randomize the order in which ports will be scanned.
|
||||
#[derive(Deserialize, Debug, StructOpt, Clone, PartialEq)]
|
||||
#[derive(Deserialize, Debug, StructOpt, Clone, Copy, PartialEq)]
|
||||
pub enum ScanOrder {
|
||||
Serial,
|
||||
Random,
|
||||
}
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
/// Represents the scripts variant.
|
||||
/// - none will avoid running any script, only portscan results will be shown.
|
||||
/// - default will run the default embedded nmap script, that's part of RustScan since the beginning.
|
||||
/// - custom will read the ScriptConfig file and the available scripts in the predefined folders
|
||||
#[derive(Deserialize, Debug, StructOpt, Clone, PartialEq, Copy)]
|
||||
pub enum ScriptsRequired {
|
||||
None,
|
||||
Default,
|
||||
Custom,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the range of ports to be scanned.
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct PortRange {
|
||||
@@ -23,10 +38,11 @@ pub struct PortRange {
|
||||
pub end: u16,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn parse_range(input: &str) -> Result<PortRange, String> {
|
||||
let range = input
|
||||
.split("-")
|
||||
.map(|x| x.parse::<u16>())
|
||||
.split('-')
|
||||
.map(str::parse)
|
||||
.collect::<Result<Vec<u16>, std::num::ParseIntError>>();
|
||||
|
||||
if range.is_err() {
|
||||
@@ -46,16 +62,17 @@ fn parse_range(input: &str) -> Result<PortRange, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
#[structopt(name = "rustscan", setting = structopt::clap::AppSettings::TrailingVarArg)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
/// Fast Port Scanner built in Rust.
|
||||
/// WARNING Do not use this program against sensitive infrastructure since the
|
||||
/// specified server may not be able to handle this many socket connections at once.
|
||||
/// - Discord https://discord.gg/GFrQsGy
|
||||
/// - GitHub https://github.com/RustScan/RustScan
|
||||
pub struct Opts {
|
||||
/// A list of comma separated CIDRs, IPs, or hosts to be scanned.
|
||||
#[structopt(use_delimiter = true)]
|
||||
/// A comma-delimited list or newline-delimited file of separated CIDRs, IPs, or hosts to be scanned.
|
||||
#[structopt(short, long, use_delimiter = true)]
|
||||
pub addresses: Vec<String>,
|
||||
|
||||
/// A list of comma separed ports to be scanned. Example: 80,443,8080.
|
||||
@@ -70,18 +87,18 @@ pub struct Opts {
|
||||
#[structopt(short, long)]
|
||||
pub no_config: bool,
|
||||
|
||||
/// Quiet mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
|
||||
/// Custom path to config file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub config_path: Option<PathBuf>,
|
||||
|
||||
/// Greppable mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
|
||||
#[structopt(short, long)]
|
||||
pub quiet: bool,
|
||||
pub greppable: bool,
|
||||
|
||||
/// Accessible mode. Turns off features which negatively affect screen readers.
|
||||
#[structopt(long)]
|
||||
pub accessible: bool,
|
||||
|
||||
/// Turns off Nmap.
|
||||
#[structopt(long)]
|
||||
pub no_nmap: bool,
|
||||
|
||||
/// The batch size for port scanning, it increases or slows the speed of
|
||||
/// scanning. Depends on the open file limit of your OS. If you do 65535
|
||||
/// it will do every port at the same time. Although, your OS may not
|
||||
@@ -93,9 +110,14 @@ pub struct Opts {
|
||||
#[structopt(short, long, default_value = "1500")]
|
||||
pub timeout: u32,
|
||||
|
||||
/// The number of tries before a port is assumed to be closed.
|
||||
/// If set to 0, rustscan will correct it to 1.
|
||||
#[structopt(long, default_value = "1")]
|
||||
pub tries: u8,
|
||||
|
||||
/// Automatically ups the ULIMIT with the value you provided.
|
||||
#[structopt(short, long)]
|
||||
pub ulimit: Option<rlimit::rlim>,
|
||||
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
|
||||
@@ -103,15 +125,24 @@ pub struct Opts {
|
||||
#[structopt(long, possible_values = &ScanOrder::variants(), case_insensitive = true, default_value = "serial")]
|
||||
pub scan_order: ScanOrder,
|
||||
|
||||
/// The Nmap arguments to run.
|
||||
/// Level of scripting required for the run.
|
||||
#[structopt(long, possible_values = &ScriptsRequired::variants(), case_insensitive = true, default_value = "default")]
|
||||
pub scripts: ScriptsRequired,
|
||||
|
||||
/// Use the top 1000 ports.
|
||||
#[structopt(long)]
|
||||
pub top: bool,
|
||||
|
||||
/// The Script arguments to run.
|
||||
/// To use the argument -A, end RustScan's args with '-- -A'.
|
||||
/// Example: 'rustscan -T 1500 127.0.0.1 -- -A -sC'.
|
||||
/// Example: 'rustscan -T 1500 -a 127.0.0.1 -- -A -sC'.
|
||||
/// This command adds -Pn -vvv -p $PORTS automatically to nmap.
|
||||
/// For things like --script '(safe and vuln)' enclose it in quotations marks \"'(safe and vuln)'\"")
|
||||
#[structopt(last = true)]
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Opts {
|
||||
pub fn read() -> Self {
|
||||
let mut opts = Opts::from_args();
|
||||
@@ -146,7 +177,10 @@ impl Opts {
|
||||
}
|
||||
}
|
||||
|
||||
merge_required!(addresses, quiet, accessible, batch_size, timeout, scan_order, command);
|
||||
merge_required!(
|
||||
addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts,
|
||||
command
|
||||
);
|
||||
}
|
||||
|
||||
fn merge_optional(&mut self, config: &Config) {
|
||||
@@ -160,28 +194,40 @@ impl Opts {
|
||||
}
|
||||
}
|
||||
|
||||
merge_optional!(ports, range, ulimit);
|
||||
// Only use top ports when the user asks for them
|
||||
if self.top && config.ports.is_some() {
|
||||
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.clone().unwrap().len());
|
||||
for entry in config.ports.clone().unwrap().keys() {
|
||||
ports.push(entry.parse().unwrap())
|
||||
}
|
||||
self.ports = Some(ports);
|
||||
}
|
||||
|
||||
merge_optional!(range, ulimit);
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct used to deserialize the options specified within our config file.
|
||||
/// These will be further merged with our command line arguments in order to
|
||||
/// generate the final Opts struct.
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
addresses: Option<Vec<String>>,
|
||||
ports: Option<Vec<u16>>,
|
||||
ports: Option<HashMap<String, u16>>,
|
||||
range: Option<PortRange>,
|
||||
quiet: Option<bool>,
|
||||
greppable: Option<bool>,
|
||||
accessible: Option<bool>,
|
||||
batch_size: Option<u16>,
|
||||
timeout: Option<u32>,
|
||||
no_nmap: Option<bool>,
|
||||
ulimit: Option<rlimit::rlim>,
|
||||
tries: Option<u8>,
|
||||
ulimit: Option<u64>,
|
||||
scan_order: Option<ScanOrder>,
|
||||
command: Option<Vec<String>>,
|
||||
scripts: Option<ScriptsRequired>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Config {
|
||||
/// Reads the configuration file with TOML format and parses it into a
|
||||
/// Config struct.
|
||||
@@ -190,44 +236,20 @@ impl Config {
|
||||
///
|
||||
/// addresses = ["127.0.0.1", "127.0.0.1"]
|
||||
/// ports = [80, 443, 8080]
|
||||
/// quiet = true
|
||||
/// greppable = true
|
||||
/// scan_order: "Serial"
|
||||
///
|
||||
pub fn read() -> Self {
|
||||
let mut paths = Vec::new();
|
||||
paths.push(match dirs::home_dir() {
|
||||
Some(mut path) => {
|
||||
path.push(".rustscan.toml");
|
||||
path
|
||||
pub fn read(custom_config_path: Option<PathBuf>) -> Self {
|
||||
let mut content = String::new();
|
||||
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,
|
||||
Err(_) => String::new(),
|
||||
}
|
||||
None => panic!("Could not infer config file path."),
|
||||
});
|
||||
|
||||
paths.push(match dirs::config_dir() {
|
||||
Some(mut path) => {
|
||||
path.push("rustscan");
|
||||
path.push("config.toml");
|
||||
path
|
||||
}
|
||||
None => panic!("Could not infer config file path."),
|
||||
});
|
||||
|
||||
let mut contents: Option<String> = None;
|
||||
for path in paths.iter().rev() {
|
||||
contents = match fs::read_to_string(path) {
|
||||
Ok(content) => {
|
||||
contents = Some(content);
|
||||
break;
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
}
|
||||
|
||||
if contents.is_none() {
|
||||
contents = Some(String::new());
|
||||
}
|
||||
|
||||
let config: Config = match toml::from_str(&contents.unwrap()) {
|
||||
let config: Config = match toml::from_str(&content) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Found {} in configuration file.\nAborting scan.\n", e);
|
||||
@@ -239,45 +261,69 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs default path to config toml
|
||||
pub fn default_config_path() -> PathBuf {
|
||||
let mut config_path = match dirs::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => panic!("Could not infer config file path."),
|
||||
};
|
||||
config_path.push(".rustscan.toml");
|
||||
config_path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Config, Opts, PortRange, ScanOrder};
|
||||
use super::{Config, Opts, PortRange, ScanOrder, ScriptsRequired};
|
||||
impl Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addresses: Some(vec!["127.0.0.1".to_owned()]),
|
||||
ports: None,
|
||||
range: None,
|
||||
greppable: Some(true),
|
||||
batch_size: Some(25_000),
|
||||
timeout: Some(1_000),
|
||||
tries: Some(1),
|
||||
ulimit: None,
|
||||
command: Some(vec!["-A".to_owned()]),
|
||||
accessible: Some(true),
|
||||
scan_order: Some(ScanOrder::Random),
|
||||
scripts: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
addresses: vec![],
|
||||
ports: None,
|
||||
range: None,
|
||||
greppable: true,
|
||||
batch_size: 0,
|
||||
timeout: 0,
|
||||
tries: 0,
|
||||
ulimit: None,
|
||||
command: vec![],
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: true,
|
||||
top: false,
|
||||
scripts: ScriptsRequired::Default,
|
||||
config_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opts_no_merge_when_config_is_ignored() {
|
||||
let mut opts = Opts {
|
||||
addresses: vec![],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: false,
|
||||
batch_size: 0,
|
||||
timeout: 0,
|
||||
ulimit: None,
|
||||
command: vec![],
|
||||
accessible: false,
|
||||
no_nmap: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: true,
|
||||
};
|
||||
|
||||
let config = Config {
|
||||
addresses: Some(vec!["127.0.0.1".to_owned()]),
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: Some(true),
|
||||
batch_size: Some(25_000),
|
||||
timeout: Some(1_000),
|
||||
ulimit: None,
|
||||
no_nmap: Some(false),
|
||||
command: Some(vec!["-A".to_owned()]),
|
||||
accessible: Some(true),
|
||||
scan_order: Some(ScanOrder::Random),
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
let config = Config::default();
|
||||
|
||||
opts.merge(&config);
|
||||
|
||||
assert_eq!(opts.addresses, vec![] as Vec<String>);
|
||||
assert_eq!(opts.quiet, false);
|
||||
assert_eq!(opts.greppable, true);
|
||||
assert_eq!(opts.accessible, false);
|
||||
assert_eq!(opts.timeout, 0);
|
||||
assert_eq!(opts.command, vec![] as Vec<String>);
|
||||
@@ -286,82 +332,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn opts_merge_required_arguments() {
|
||||
let mut opts = Opts {
|
||||
addresses: vec![],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: false,
|
||||
batch_size: 0,
|
||||
timeout: 0,
|
||||
ulimit: None,
|
||||
command: vec![],
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_nmap: false,
|
||||
no_config: false,
|
||||
};
|
||||
|
||||
let config = Config {
|
||||
addresses: Some(vec!["127.0.0.1".to_owned()]),
|
||||
ports: None,
|
||||
no_nmap: Some(false),
|
||||
range: None,
|
||||
quiet: Some(true),
|
||||
batch_size: Some(25_000),
|
||||
timeout: Some(1_000),
|
||||
ulimit: None,
|
||||
command: Some(vec!["-A".to_owned()]),
|
||||
accessible: Some(true),
|
||||
scan_order: Some(ScanOrder::Random),
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
let config = Config::default();
|
||||
|
||||
opts.merge_required(&config);
|
||||
|
||||
assert_eq!(opts.addresses, config.addresses.unwrap());
|
||||
assert_eq!(opts.quiet, config.quiet.unwrap());
|
||||
assert_eq!(opts.greppable, config.greppable.unwrap());
|
||||
assert_eq!(opts.timeout, config.timeout.unwrap());
|
||||
assert_eq!(opts.command, config.command.unwrap());
|
||||
assert_eq!(opts.accessible, config.accessible.unwrap());
|
||||
assert_eq!(opts.scan_order, config.scan_order.unwrap());
|
||||
assert_eq!(opts.scripts, ScriptsRequired::Default)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opts_merge_optional_arguments() {
|
||||
let mut opts = Opts {
|
||||
addresses: vec![],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: false,
|
||||
batch_size: 0,
|
||||
timeout: 0,
|
||||
ulimit: None,
|
||||
command: vec![],
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_nmap: false,
|
||||
no_config: false,
|
||||
};
|
||||
|
||||
let config = Config {
|
||||
addresses: None,
|
||||
ports: Some(vec![80, 403]),
|
||||
range: Some(PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
}),
|
||||
quiet: None,
|
||||
batch_size: None,
|
||||
timeout: None,
|
||||
no_nmap: Some(false),
|
||||
ulimit: Some(1_000),
|
||||
command: None,
|
||||
accessible: None,
|
||||
scan_order: None,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
let mut config = Config::default();
|
||||
config.range = Some(PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
});
|
||||
config.ulimit = Some(1_000);
|
||||
|
||||
opts.merge_optional(&config);
|
||||
|
||||
assert_eq!(opts.ports, config.ports);
|
||||
assert_eq!(opts.range, config.range);
|
||||
assert_eq!(opts.ulimit, config.ulimit);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#![deny(clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::doc_markdown, clippy::if_not_else, clippy::non_ascii_literal)]
|
||||
|
||||
extern crate shell_words;
|
||||
|
||||
mod tui;
|
||||
|
||||
mod input;
|
||||
use input::{Config, Opts, PortRange, ScanOrder};
|
||||
use input::{Config, Opts, PortRange, ScanOrder, ScriptsRequired};
|
||||
|
||||
mod scanner;
|
||||
use scanner::Scanner;
|
||||
@@ -11,64 +15,105 @@ use scanner::Scanner;
|
||||
mod port_strategy;
|
||||
use port_strategy::PortStrategy;
|
||||
|
||||
mod benchmark;
|
||||
use benchmark::{Benchmark, NamedTimer};
|
||||
|
||||
mod scripts;
|
||||
use scripts::{init_scripts, Script, ScriptFile};
|
||||
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
use colorful::Color;
|
||||
use colorful::Colorful;
|
||||
use colorful::{Color, Colorful};
|
||||
use futures::executor::block_on;
|
||||
use rlimit::Resource;
|
||||
use rlimit::{getrlimit, setrlimit};
|
||||
use std::collections::HashMap;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::process::Command;
|
||||
use std::{net::IpAddr, time::Duration};
|
||||
use std::fs::File;
|
||||
use std::io::{prelude::*, BufReader};
|
||||
use std::net::{IpAddr, ToSocketAddrs};
|
||||
use std::path::Path;
|
||||
use std::string::ToString;
|
||||
use std::time::Duration;
|
||||
use trust_dns_resolver::{
|
||||
config::{ResolverConfig, ResolverOpts},
|
||||
Resolver,
|
||||
};
|
||||
|
||||
extern crate colorful;
|
||||
extern crate dirs;
|
||||
|
||||
// Average value for Ubuntu
|
||||
const DEFAULT_FILE_DESCRIPTORS_LIMIT: rlimit::rlim = 8000;
|
||||
#[cfg(unix)]
|
||||
const DEFAULT_FILE_DESCRIPTORS_LIMIT: u64 = 8000;
|
||||
// Safest batch size based on experimentation
|
||||
const AVERAGE_BATCH_SIZE: rlimit::rlim = 3000;
|
||||
const AVERAGE_BATCH_SIZE: u16 = 3000;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
/// Faster Nmap scanning with Rust
|
||||
/// If you're looking for the actual scanning, check out the module Scanner
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let mut benchmarks = Benchmark::init();
|
||||
let mut rustscan_bench = NamedTimer::start("RustScan");
|
||||
|
||||
let mut opts: Opts = Opts::read();
|
||||
let config = Config::read();
|
||||
let config = Config::read(opts.config_path.clone());
|
||||
opts.merge(&config);
|
||||
dbg!(&opts);
|
||||
|
||||
info!("Main() `opts` arguments are {:?}", opts);
|
||||
debug!("Main() `opts` arguments are {:?}", opts);
|
||||
|
||||
if !opts.quiet && !opts.accessible {
|
||||
print_opening();
|
||||
let scripts_to_run: Vec<ScriptFile> = match init_scripts(opts.scripts) {
|
||||
Ok(scripts_to_run) => scripts_to_run,
|
||||
Err(e) => {
|
||||
warning!(
|
||||
format!("Initiating scripts failed!\n{}", e),
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Scripts initialized {:?}", &scripts_to_run);
|
||||
|
||||
if !opts.greppable && !opts.accessible {
|
||||
print_opening(&opts);
|
||||
}
|
||||
|
||||
let ips: Vec<IpAddr> = parse_addresses(&opts);
|
||||
|
||||
if ips.is_empty() {
|
||||
warning!("No IPs could be resolved, aborting scan.", false);
|
||||
warning!(
|
||||
"No IPs could be resolved, aborting scan.",
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let ulimit: rlimit::rlim = adjust_ulimit_size(&opts);
|
||||
let batch_size: u16 = infer_batch_size(&opts, ulimit);
|
||||
#[cfg(unix)]
|
||||
let batch_size: u16 = infer_batch_size(&opts, adjust_ulimit_size(&opts));
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let batch_size: u16 = AVERAGE_BATCH_SIZE;
|
||||
|
||||
let scanner = Scanner::new(
|
||||
&ips,
|
||||
batch_size,
|
||||
Duration::from_millis(opts.timeout.into()),
|
||||
opts.quiet,
|
||||
PortStrategy::pick(opts.range, opts.ports, opts.scan_order),
|
||||
opts.tries,
|
||||
opts.greppable,
|
||||
PortStrategy::pick(&opts.range, opts.ports, opts.scan_order),
|
||||
opts.accessible,
|
||||
);
|
||||
debug!("Scanner finished building: {:?}", scanner);
|
||||
|
||||
let mut portscan_bench = NamedTimer::start("Portscan");
|
||||
let scan_result = block_on(scanner.run());
|
||||
portscan_bench.end();
|
||||
benchmarks.push(portscan_bench);
|
||||
|
||||
let mut ports_per_ip = HashMap::new();
|
||||
|
||||
for socket in scan_result {
|
||||
@@ -86,223 +131,293 @@ fn main() {
|
||||
// If we got here it means the IP was not found within the HashMap, this
|
||||
// means the scan couldn't find any open ports for it.
|
||||
|
||||
let x = format!("{} Looks like I didn't find any open ports for {:?}. This is usually caused by a high batch size.
|
||||
\n*I used {} batch size, consider lowering to {} with {} or a comfortable number for your system.
|
||||
let x = format!("Looks like I didn't find any open ports for {:?}. This is usually caused by a high batch size.
|
||||
\n*I used {} batch size, consider lowering it with {} or a comfortable number for your system.
|
||||
\n Alternatively, increase the timeout if your ping is high. Rustscan -t 2000 for 2000 milliseconds (2s) timeout.\n",
|
||||
"ERROR",
|
||||
ip,
|
||||
opts.batch_size,
|
||||
(opts.batch_size / 2).to_string(),
|
||||
"'rustscan -b <batch_size> <ip address>'");
|
||||
warning!(x, opts.quiet);
|
||||
"'rustscan -b <batch_size> -a <ip address>'");
|
||||
warning!(x, opts.greppable, opts.accessible);
|
||||
}
|
||||
|
||||
for (ip, ports) in ports_per_ip.iter_mut() {
|
||||
let nmap_str_ports: Vec<String> = ports.into_iter().map(|port| port.to_string()).collect();
|
||||
|
||||
detail!("Starting Nmap", opts.quiet);
|
||||
let mut script_bench = NamedTimer::start("Scripts");
|
||||
for (ip, ports) in &ports_per_ip {
|
||||
let vec_str_ports: Vec<String> = ports.iter().map(ToString::to_string).collect();
|
||||
|
||||
// nmap port style is 80,443. Comma separated with no spaces.
|
||||
let ports_str = nmap_str_ports.join(",");
|
||||
let ports_str = vec_str_ports.join(",");
|
||||
|
||||
// if quiet mode is on nmap should not be spawned
|
||||
if opts.quiet {
|
||||
println!("{}", ports_str);
|
||||
// if option scripts is none, no script will be spawned
|
||||
if opts.greppable || opts.scripts == ScriptsRequired::None {
|
||||
println!("{} -> [{}]", &ip, ports_str);
|
||||
continue;
|
||||
}
|
||||
detail!("Starting Script(s)", opts.greppable, opts.accessible);
|
||||
|
||||
let addr = ip.to_string();
|
||||
let user_nmap_args =
|
||||
shell_words::split(&opts.command.join(" ")).expect("failed to parse nmap arguments");
|
||||
let nmap_args = build_nmap_arguments(&addr, &ports_str, &user_nmap_args, ip.is_ipv6());
|
||||
// Run all the scripts we found and parsed based on the script config file tags field.
|
||||
for mut script_f in scripts_to_run.clone() {
|
||||
// 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);
|
||||
if script_f.call_format.is_some() {
|
||||
let mut call_f = script_f.call_format.unwrap();
|
||||
call_f.push(' ');
|
||||
call_f.push_str(user_extra_args);
|
||||
output!(
|
||||
format!("Running script {:?} on ip {}\nDepending on the complexity of the script, results may take some time to appear.", call_f, &ip),
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
debug!("Call format {}", call_f);
|
||||
script_f.call_format = Some(call_f);
|
||||
}
|
||||
}
|
||||
|
||||
output!(format!(
|
||||
"The Nmap command to be run is nmap {}\n",
|
||||
&nmap_args.join(" ")
|
||||
));
|
||||
|
||||
// Runs the nmap command and spawns it as a process.
|
||||
let mut child = Command::new("nmap")
|
||||
.args(&nmap_args)
|
||||
.spawn()
|
||||
.expect("failed to execute nmap process");
|
||||
|
||||
child.wait().expect("failed to wait on nmap process");
|
||||
// Building the script with the arguments from the ScriptFile, and ip-ports.
|
||||
let script = Script::build(
|
||||
script_f.path,
|
||||
*ip,
|
||||
ports.clone(),
|
||||
script_f.port,
|
||||
script_f.ports_separator,
|
||||
script_f.tags,
|
||||
script_f.call_format,
|
||||
);
|
||||
match script.run() {
|
||||
Ok(script_result) => {
|
||||
detail!(script_result.to_string(), opts.greppable, opts.accessible);
|
||||
}
|
||||
Err(e) => {
|
||||
warning!(&format!("Error {}", e), opts.greppable, opts.accessible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To use the runtime benchmark, run the process as: RUST_LOG=info ./rustscan
|
||||
script_bench.end();
|
||||
benchmarks.push(script_bench);
|
||||
rustscan_bench.end();
|
||||
benchmarks.push(rustscan_bench);
|
||||
debug!("Benchmarks raw {:?}", benchmarks);
|
||||
info!("{}", benchmarks.summary());
|
||||
}
|
||||
|
||||
/// Prints the opening title of RustScan
|
||||
fn print_opening() {
|
||||
info!("Printing opening");
|
||||
let s = r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-.
|
||||
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
|
||||
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
|
||||
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
|
||||
Faster Nmap scanning with Rust."#;
|
||||
fn print_opening(opts: &Opts) {
|
||||
debug!("Printing opening");
|
||||
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#"________________________________________
|
||||
: https://discord.gg/GFrQsGy :
|
||||
: 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!();
|
||||
|
||||
let config_path = match dirs::config_dir() {
|
||||
Some(mut path) => {
|
||||
path.push("rustscan");
|
||||
path.push("config.toml");
|
||||
path
|
||||
}
|
||||
None => panic!("Couldn't find config dir."),
|
||||
};
|
||||
let config_path = opts
|
||||
.config_path
|
||||
.clone()
|
||||
.unwrap_or_else(input::default_config_path);
|
||||
|
||||
detail!(format!(
|
||||
"{} {:?}",
|
||||
"The config file is expected to be at", config_path
|
||||
));
|
||||
detail!(
|
||||
format!("The config file is expected to be at {:?}", config_path),
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
}
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn build_nmap_arguments<'a>(
|
||||
addr: &'a str,
|
||||
ports: &'a str,
|
||||
user_args: &'a Vec<String>,
|
||||
is_ipv6: bool,
|
||||
) -> Vec<&'a str> {
|
||||
let mut arguments: Vec<&str> = user_args.iter().map(AsRef::as_ref).collect();
|
||||
arguments.push("-vvv");
|
||||
|
||||
if is_ipv6 {
|
||||
arguments.push("-6");
|
||||
/// Goes through all possible IP inputs (files or via argparsing)
|
||||
/// Parses the string(s) into IPs
|
||||
fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
let mut unresolved_addresses: Vec<&str> = Vec::new();
|
||||
let backup_resolver =
|
||||
Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap();
|
||||
|
||||
for address in &input.addresses {
|
||||
let parsed_ips = parse_address(address, &backup_resolver);
|
||||
if !parsed_ips.is_empty() {
|
||||
ips.extend(parsed_ips);
|
||||
} else {
|
||||
unresolved_addresses.push(address);
|
||||
}
|
||||
}
|
||||
|
||||
arguments.push("-p");
|
||||
arguments.push(ports);
|
||||
arguments.push(addr);
|
||||
// If we got to this point this can only be a file path or the wrong input.
|
||||
for file_path in unresolved_addresses {
|
||||
let file_path = Path::new(file_path);
|
||||
|
||||
arguments
|
||||
}
|
||||
if !file_path.is_file() {
|
||||
warning!(
|
||||
format!("Host {:?} could not be resolved.", file_path),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
|
||||
fn parse_addresses(opts: &Opts) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
continue;
|
||||
}
|
||||
|
||||
for ip_or_host in &opts.addresses {
|
||||
match IpCidr::from_str(ip_or_host) {
|
||||
Ok(cidr) => cidr.iter().for_each(|ip| ips.push(ip)),
|
||||
_ => match format!("{}:{}", &ip_or_host, 80).to_socket_addrs() {
|
||||
Ok(mut iter) => ips.push(iter.nth(0).unwrap().ip()),
|
||||
_ => {
|
||||
let failed_to_resolve = format!("Host {:?} could not be resolved.", ip_or_host);
|
||||
warning!(failed_to_resolve, opts.quiet);
|
||||
}
|
||||
},
|
||||
if let Ok(x) = read_ips_from_file(file_path, &backup_resolver) {
|
||||
ips.extend(x);
|
||||
} else {
|
||||
warning!(
|
||||
format!("Host {:?} could not be resolved.", file_path),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
fn adjust_ulimit_size(opts: &Opts) -> rlimit::rlim {
|
||||
if opts.ulimit.is_some() {
|
||||
let limit: rlimit::rlim = opts.ulimit.unwrap();
|
||||
/// Given a string, parse it as an host, IP address, or CIDR.
|
||||
/// This allows us to pass files as hosts or cidr or IPs easily
|
||||
/// Call this everytime you have a possible IP_or_host
|
||||
fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
|
||||
IpCidr::from_str(&address)
|
||||
.map(|cidr| cidr.iter().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))
|
||||
}
|
||||
|
||||
match setrlimit(Resource::NOFILE, limit, limit) {
|
||||
Ok(_) => {
|
||||
detail!(
|
||||
format!("Automatically increasing ulimit value to {}.", limit),
|
||||
opts.quiet
|
||||
);
|
||||
}
|
||||
Err(_) => println!("{}", "ERROR. Failed to set ulimit value."),
|
||||
/// Uses DNS to get the IPS assiocated with host
|
||||
fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<std::net::IpAddr> = Vec::new();
|
||||
|
||||
if let Ok(addrs) = source.to_socket_addrs() {
|
||||
for ip in addrs {
|
||||
ips.push(ip.ip());
|
||||
}
|
||||
} else if let Ok(addrs) = backup_resolver.lookup_ip(source) {
|
||||
ips.extend(addrs.iter());
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
/// Parses an input file of IPs and uses those
|
||||
fn read_ips_from_file(
|
||||
ips: &std::path::Path,
|
||||
backup_resolver: &Resolver,
|
||||
) -> Result<Vec<std::net::IpAddr>, std::io::Error> {
|
||||
let file = File::open(ips)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut ips: Vec<std::net::IpAddr> = Vec::new();
|
||||
|
||||
for address_line in reader.lines() {
|
||||
if let Ok(address) = address_line {
|
||||
ips.extend(parse_address(&address, backup_resolver));
|
||||
} else {
|
||||
debug!("Line in file is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
let (rlim, _) = getrlimit(Resource::NOFILE).unwrap();
|
||||
|
||||
rlim
|
||||
Ok(ips)
|
||||
}
|
||||
|
||||
fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {
|
||||
let mut batch_size: rlimit::rlim = opts.batch_size.into();
|
||||
#[cfg(unix)]
|
||||
fn adjust_ulimit_size(opts: &Opts) -> u64 {
|
||||
use rlimit::Resource;
|
||||
if let Some(limit) = opts.ulimit {
|
||||
if Resource::NOFILE.set(limit, limit).is_ok() {
|
||||
detail!(
|
||||
format!("Automatically increasing ulimit value to {}.", limit),
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
} else {
|
||||
warning!(
|
||||
"ERROR. Failed to set ulimit value.",
|
||||
opts.greppable,
|
||||
opts.accessible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let (soft, _) = Resource::NOFILE.get().unwrap();
|
||||
soft
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
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 {
|
||||
warning!("File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers",
|
||||
opts.quiet
|
||||
opts.greppable, opts.accessible
|
||||
);
|
||||
|
||||
// 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
|
||||
warning!("Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. ");
|
||||
warning!("Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. ", opts.greppable, opts.accessible);
|
||||
info!("Halving batch_size because ulimit is smaller than average batch size");
|
||||
batch_size = ulimit / 2
|
||||
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
|
||||
batch_size = ulimit - 100;
|
||||
}
|
||||
}
|
||||
// When the ulimit is higher than the batch size let the user know that the
|
||||
// batch size can be increased unless they specified the ulimit themselves.
|
||||
else if ulimit + 2 > batch_size && (opts.ulimit.is_none()) {
|
||||
detail!(format!(
|
||||
"File limit higher than batch size. Can increase speed by increasing batch size '-b {}'.",
|
||||
ulimit - 100
|
||||
), opts.quiet);
|
||||
detail!(format!("File limit higher than batch size. Can increase speed by increasing batch size '-b {}'.", ulimit - 100),
|
||||
opts.greppable, opts.accessible);
|
||||
}
|
||||
|
||||
batch_size as u16
|
||||
batch_size
|
||||
.try_into()
|
||||
.expect("Couldn't fit the batch size into a u16.")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
adjust_ulimit_size, infer_batch_size, parse_addresses, print_opening, Opts, ScanOrder,
|
||||
};
|
||||
use crate::{adjust_ulimit_size, infer_batch_size, parse_addresses, print_opening, Opts};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[test]
|
||||
fn batch_size_lowered() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 50_000,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 120);
|
||||
|
||||
assert!(batch_size < 50_000);
|
||||
assert!(batch_size < opts.batch_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_size_lowered_average_size() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 50_000,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 9_000);
|
||||
|
||||
assert!(batch_size == 3_000);
|
||||
@@ -311,20 +426,8 @@ mod tests {
|
||||
fn batch_size_equals_ulimit_lowered() {
|
||||
// because ulimit and batch size are same size, batch size is lowered
|
||||
// to ULIMIT - 100
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 50_000,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
let batch_size = infer_batch_size(&opts, 5_000);
|
||||
|
||||
assert!(batch_size == 4_900);
|
||||
@@ -332,68 +435,36 @@ mod tests {
|
||||
#[test]
|
||||
fn batch_size_adjusted_2000() {
|
||||
// ulimit == batch_size
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 50_000,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 50_000;
|
||||
opts.ulimit = Some(2_000);
|
||||
let batch_size = adjust_ulimit_size(&opts);
|
||||
|
||||
assert!(batch_size == 2_000);
|
||||
}
|
||||
#[test]
|
||||
fn test_print_opening_no_panic() {
|
||||
let mut opts = Opts::default();
|
||||
opts.ulimit = Some(2_000);
|
||||
// print opening should not panic
|
||||
print_opening();
|
||||
assert!(1 == 1);
|
||||
print_opening(&opts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_high_ulimit_no_quiet_mode() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: false,
|
||||
batch_size: 10,
|
||||
timeout: 1_000,
|
||||
ulimit: None,
|
||||
command: Vec::new(),
|
||||
accessible: true,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
fn test_high_ulimit_no_greppable_mode() {
|
||||
let mut opts = Opts::default();
|
||||
opts.batch_size = 10;
|
||||
opts.greppable = false;
|
||||
|
||||
infer_batch_size(&opts, 1_000_000);
|
||||
let batch_size = infer_batch_size(&opts, 1_000_000);
|
||||
|
||||
assert!(1 == 1);
|
||||
assert!(batch_size == opts.batch_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 10,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
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!(
|
||||
@@ -410,20 +481,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_correct_host_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["google.com".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 10,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["google.com".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 1);
|
||||
@@ -431,20 +490,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()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 10,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
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),]);
|
||||
@@ -452,22 +499,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_incorrect_addresses() {
|
||||
let opts = Opts {
|
||||
addresses: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
|
||||
ports: None,
|
||||
range: None,
|
||||
quiet: true,
|
||||
batch_size: 10,
|
||||
timeout: 1_000,
|
||||
ulimit: Some(2_000),
|
||||
command: Vec::new(),
|
||||
accessible: false,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: false,
|
||||
no_nmap: false,
|
||||
};
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.is_empty(), true);
|
||||
}
|
||||
#[test]
|
||||
fn parse_hosts_file_and_incorrect_hosts() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
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 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 mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/naughty_string.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use range_iterator::RangeIterator;
|
||||
///
|
||||
/// Right now all these options involve ranges, but in the future
|
||||
/// it will also contain custom lists of ports.
|
||||
#[derive(Debug)]
|
||||
pub enum PortStrategy {
|
||||
Manual(Vec<u16>),
|
||||
Serial(SerialRange),
|
||||
@@ -15,7 +16,7 @@ pub enum PortStrategy {
|
||||
}
|
||||
|
||||
impl PortStrategy {
|
||||
pub fn pick(range: Option<PortRange>, ports: Option<Vec<u16>>, order: ScanOrder) -> Self {
|
||||
pub fn pick(range: &Option<PortRange>, ports: Option<Vec<u16>>, order: ScanOrder) -> Self {
|
||||
match order {
|
||||
ScanOrder::Serial if ports.is_none() => {
|
||||
let range = range.as_ref().unwrap();
|
||||
@@ -58,6 +59,7 @@ trait RangeOrder {
|
||||
|
||||
/// As the name implies SerialRange will always generate a vector in
|
||||
/// ascending order.
|
||||
#[derive(Debug)]
|
||||
pub struct SerialRange {
|
||||
start: u16,
|
||||
end: u16,
|
||||
@@ -71,6 +73,7 @@ impl RangeOrder for SerialRange {
|
||||
|
||||
/// As the name implies RandomRange will always generate a vector with
|
||||
/// a random order. This vector is built following the LCG algorithm.
|
||||
#[derive(Debug)]
|
||||
pub struct RandomRange {
|
||||
start: u16,
|
||||
end: u16,
|
||||
@@ -99,7 +102,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_strategy_with_range() {
|
||||
let range = PortRange { start: 1, end: 100 };
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Serial);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Serial);
|
||||
let result = strategy.order();
|
||||
let expected_range = (1..100).into_iter().collect::<Vec<u16>>();
|
||||
assert_eq!(expected_range, result);
|
||||
@@ -107,7 +110,7 @@ mod tests {
|
||||
#[test]
|
||||
fn random_strategy_with_range() {
|
||||
let range = PortRange { start: 1, end: 100 };
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let mut result = strategy.order();
|
||||
let expected_range = (1..100).into_iter().collect::<Vec<u16>>();
|
||||
assert_ne!(expected_range, result);
|
||||
@@ -118,14 +121,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serial_strategy_with_ports() {
|
||||
let strategy = PortStrategy::pick(None, Some(vec![80, 443]), ScanOrder::Serial);
|
||||
let strategy = PortStrategy::pick(&None, Some(vec![80, 443]), ScanOrder::Serial);
|
||||
let result = strategy.order();
|
||||
assert_eq!(vec![80, 443], result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_strategy_with_ports() {
|
||||
let strategy = PortStrategy::pick(None, Some((1..10).collect()), ScanOrder::Random);
|
||||
let strategy = PortStrategy::pick(&None, Some((1..10).collect()), ScanOrder::Random);
|
||||
let mut result = strategy.order();
|
||||
let expected_range = (1..10).into_iter().collect::<Vec<u16>>();
|
||||
assert_ne!(expected_range, result);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use gcd::Gcd;
|
||||
use rand::Rng;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct RangeIterator {
|
||||
active: bool,
|
||||
@@ -60,7 +61,11 @@ impl Iterator for RangeIterator {
|
||||
}
|
||||
|
||||
self.normalized_pick = next_pick;
|
||||
Some((self.actual_start + current_pick) as u16)
|
||||
Some(
|
||||
(self.actual_start + current_pick)
|
||||
.try_into()
|
||||
.expect("Could not convert u32 to u16"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use super::PortStrategy;
|
||||
|
||||
mod socket_iterator;
|
||||
use socket_iterator::SocketIterator;
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::TcpStream;
|
||||
use async_std::prelude::*;
|
||||
use colored::*;
|
||||
use colored::Colorize;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
io::ErrorKind,
|
||||
collections::HashSet,
|
||||
net::{IpAddr, Shutdown, SocketAddr},
|
||||
num::NonZeroU8,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// The class for the scanner
|
||||
@@ -16,14 +20,17 @@ use std::{
|
||||
/// start & end is where the port scan starts and ends
|
||||
/// 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.
|
||||
/// Quiet is whether or not RustScan should print things, or wait until the end to print only open ports.
|
||||
/// greppable is whether or not RustScan should print things, or wait until the end to print only the ip and open ports.
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[derive(Debug)]
|
||||
pub struct Scanner {
|
||||
ips: Vec<IpAddr>,
|
||||
batch_size: u16,
|
||||
timeout: Duration,
|
||||
quiet: bool,
|
||||
tries: NonZeroU8,
|
||||
greppable: bool,
|
||||
port_strategy: PortStrategy,
|
||||
accessible: bool,
|
||||
}
|
||||
|
||||
impl Scanner {
|
||||
@@ -31,15 +38,19 @@ impl Scanner {
|
||||
ips: &[IpAddr],
|
||||
batch_size: u16,
|
||||
timeout: Duration,
|
||||
quiet: bool,
|
||||
tries: u8,
|
||||
greppable: bool,
|
||||
port_strategy: PortStrategy,
|
||||
accessible: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
batch_size,
|
||||
timeout,
|
||||
quiet,
|
||||
tries: NonZeroU8::new(std::cmp::max(tries, 1)).unwrap(),
|
||||
greppable,
|
||||
port_strategy,
|
||||
ips: ips.iter().map(|ip| ip.to_owned()).collect(),
|
||||
ips: ips.iter().map(ToOwned::to_owned).collect(),
|
||||
accessible,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,71 +59,97 @@ impl Scanner {
|
||||
/// Returns all open ports as Vec<u16>
|
||||
pub async fn run(&self) -> Vec<SocketAddr> {
|
||||
let ports: Vec<u16> = self.port_strategy.order();
|
||||
let batch_per_ip: usize = self.batch_size as usize / self.ips.len();
|
||||
let mut socket_iterator: SocketIterator = SocketIterator::new(&self.ips, &ports);
|
||||
let mut open_sockets: Vec<SocketAddr> = Vec::new();
|
||||
|
||||
for batch in ports.chunks(batch_per_ip) {
|
||||
let mut sockets = self.scan_ports(batch).await;
|
||||
open_sockets.append(&mut sockets);
|
||||
}
|
||||
|
||||
open_sockets
|
||||
}
|
||||
|
||||
/// Given a slice of sockets, scan them all.
|
||||
/// Returns a vector of open sockets.
|
||||
async fn scan_ports(&self, ports: &[u16]) -> Vec<SocketAddr> {
|
||||
let mut ftrs = FuturesUnordered::new();
|
||||
for port in ports {
|
||||
for ip in &self.ips {
|
||||
ftrs.push(self.scan_socket(SocketAddr::new(*ip, *port)));
|
||||
let mut errors: HashSet<String> = HashSet::with_capacity(self.ips.len() * 1000);
|
||||
|
||||
for _ in 0..self.batch_size {
|
||||
if let Some(socket) = socket_iterator.next() {
|
||||
ftrs.push(self.scan_socket(socket));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut open_sockets: Vec<SocketAddr> = Vec::new();
|
||||
debug!("Start scanning sockets. \nBatch size {}\nNumber of ip-s {}\nNumber of ports {}\nTargets all together {} ",
|
||||
self.batch_size,
|
||||
self.ips.len(),
|
||||
&ports.len(),
|
||||
(self.ips.len() * ports.len()));
|
||||
|
||||
while let Some(result) = ftrs.next().await {
|
||||
if let Some(socket) = socket_iterator.next() {
|
||||
ftrs.push(self.scan_socket(socket));
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok(socket) => open_sockets.push(socket),
|
||||
_ => {}
|
||||
Err(e) => {
|
||||
let error_string = e.to_string();
|
||||
if errors.len() < self.ips.len() * 1000 {
|
||||
errors.insert(error_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Typical socket connection errors {:?}", errors);
|
||||
debug!("Open Sockets found: {:?}", &open_sockets);
|
||||
open_sockets
|
||||
}
|
||||
|
||||
/// Given a port, scan it.
|
||||
/// Given a socket, scan it self.tries times.
|
||||
/// Turns the address into a SocketAddr
|
||||
/// Deals with the <result> type
|
||||
/// If it experiences error ErrorKind::Other then too many files are open and it Panics!
|
||||
/// ese any other error, it returns the error in Result as a string
|
||||
/// If no errors occur, it returns the port number in Result to signify the port is open.
|
||||
/// Else any other error, it returns the error in Result as a string
|
||||
/// If no errors occur, it returns the port number in Result to signify the port is open.
|
||||
/// This function mainly deals with the logic of Results handling.
|
||||
/// # Example
|
||||
///
|
||||
/// self.scan_port(10:u16)
|
||||
/// self.scan_socket(socket)
|
||||
///
|
||||
/// Note: `self` must contain `self.ip`.
|
||||
async fn scan_socket(&self, socket: SocketAddr) -> io::Result<SocketAddr> {
|
||||
match self.connect(socket).await {
|
||||
Ok(x) => {
|
||||
// match stream_result.shutdown(Shutdown::Both)
|
||||
info!("Shutting down stream");
|
||||
match x.shutdown(Shutdown::Both) {
|
||||
_ => {}
|
||||
}
|
||||
if !self.quiet {
|
||||
println!("Open {}", socket.to_string().purple());
|
||||
}
|
||||
let tries = self.tries.get();
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Other => {
|
||||
panic!("Too many open files. Please reduce batch size. The default is 5000. Try -b 2500.");
|
||||
for nr_try in 1..=tries {
|
||||
match self.connect(socket).await {
|
||||
Ok(x) => {
|
||||
debug!(
|
||||
"Connection was successful, shutting down stream {}",
|
||||
&socket
|
||||
);
|
||||
if let Err(e) = x.shutdown(Shutdown::Both) {
|
||||
debug!("Shutdown stream error {}", &e);
|
||||
}
|
||||
if !self.greppable {
|
||||
if self.accessible {
|
||||
println!("Open {}", socket.to_string());
|
||||
} else {
|
||||
println!("Open {}", socket.to_string().purple());
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Return Ok after {} tries", nr_try);
|
||||
return Ok(socket);
|
||||
}
|
||||
_ => Err(io::Error::new(io::ErrorKind::Other, e.to_string())),
|
||||
},
|
||||
Err(e) => {
|
||||
let mut error_string = e.to_string();
|
||||
|
||||
if error_string.to_lowercase().contains("too many open files") {
|
||||
panic!("Too many open files. Please reduce batch size. The default is 5000. Try -b 2500.");
|
||||
}
|
||||
|
||||
if nr_try == tries {
|
||||
error_string.push(' ');
|
||||
error_string.push_str(&socket.ip().to_string());
|
||||
return Err(io::Error::new(io::ErrorKind::Other, error_string));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
/// Performs the connection to the socket with timeout
|
||||
@@ -151,8 +188,16 @@ mod tests {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
assert_eq!(1, 1);
|
||||
@@ -165,8 +210,16 @@ mod tests {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
assert_eq!(1, 1);
|
||||
@@ -178,8 +231,16 @@ mod tests {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
@@ -190,8 +251,16 @@ mod tests {
|
||||
start: 400,
|
||||
end: 445,
|
||||
};
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
@@ -205,8 +274,16 @@ mod tests {
|
||||
start: 400,
|
||||
end: 600,
|
||||
};
|
||||
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
use itertools::{iproduct, Product};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
pub struct SocketIterator<'s> {
|
||||
// product_it is a cartesian product iterator over
|
||||
// the slices of ports and IP addresses.
|
||||
//
|
||||
// The IP/port order is intentionally reversed here since we want
|
||||
// the itertools::iproduct! macro below to generate the pairs with
|
||||
// all the IPs for one port before moving on to the next one
|
||||
// ("hold the port, go through all the IPs, then advance the port...").
|
||||
// See also the comments in the iterator implementation for an example.
|
||||
product_it:
|
||||
Product<Box<std::slice::Iter<'s, u16>>, Box<std::slice::Iter<'s, std::net::IpAddr>>>,
|
||||
}
|
||||
|
||||
/// An iterator that receives a slice of IPs and ports and returns a Socket
|
||||
/// for each IP and port pair until all of these combinations are exhausted.
|
||||
/// The goal of this iterator is to go over every IP and port combination
|
||||
/// wihout generating a big memory footprint. The alternative would be
|
||||
/// generating a vector containing all these combinations.
|
||||
impl<'s> SocketIterator<'s> {
|
||||
pub fn new(ips: &'s [IpAddr], ports: &'s [u16]) -> Self {
|
||||
let ports_it = Box::new(ports.iter());
|
||||
let ips_it = Box::new(ips.iter());
|
||||
Self {
|
||||
product_it: iproduct!(ports_it, ips_it),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Iterator for SocketIterator<'s> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
/// Returns a socket based on the combination of one of the provided
|
||||
/// IPs and ports or None when these combinations are exhausted. Every
|
||||
/// IP will have the same port until a port is incremented.
|
||||
///
|
||||
/// let it = SocketIterator::new(&["127.0.0.1", "192.168.0.1"], &[80, 443]);
|
||||
/// it.next(); // 127.0.0.1:80
|
||||
/// it.next(); // 192.168.0.1:80
|
||||
/// it.next(); // 127.0.0.1:443
|
||||
/// it.next(); // 192.168.0.1:443
|
||||
/// it.next(); // None
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.product_it.next() {
|
||||
None => None,
|
||||
Some((port, ip)) => Some(SocketAddr::new(*ip, *port)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SocketIterator;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
#[test]
|
||||
fn goes_through_every_ip_port_combination() {
|
||||
let addrs = vec![
|
||||
"127.0.0.1".parse::<IpAddr>().unwrap(),
|
||||
"192.168.0.1".parse::<IpAddr>().unwrap(),
|
||||
];
|
||||
let ports: Vec<u16> = vec![22, 80, 443];
|
||||
let mut it = SocketIterator::new(&addrs, &ports);
|
||||
|
||||
assert_eq!(Some(SocketAddr::new(addrs[0], ports[0])), it.next());
|
||||
assert_eq!(Some(SocketAddr::new(addrs[1], ports[0])), it.next());
|
||||
assert_eq!(Some(SocketAddr::new(addrs[0], ports[1])), it.next());
|
||||
assert_eq!(Some(SocketAddr::new(addrs[1], ports[1])), it.next());
|
||||
assert_eq!(Some(SocketAddr::new(addrs[0], ports[2])), it.next());
|
||||
assert_eq!(Some(SocketAddr::new(addrs[1], ports[2])), it.next());
|
||||
assert_eq!(None, it.next());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
//! Scripting engine to run scripts based on tags.
|
||||
//! This module serves to filter and run the scripts selected by the user.
|
||||
//!
|
||||
//! A new commandline and configuration file option was added.
|
||||
//!
|
||||
//! --scripts
|
||||
//!
|
||||
//! default
|
||||
//! This is the default behavior, like as it was from the beginning of RustScan.
|
||||
//! The user do not have to chose anything for this. This is the only script embedded in RustScan running as default.
|
||||
//!
|
||||
//! none
|
||||
//! The user have to use the --scripts none commandline argument or scripts = "none" in the config file.
|
||||
//! None of the scripts will run, this replaces the removed --no-nmap option.
|
||||
//!
|
||||
//! custom
|
||||
//! The user have to use the --scripts custom commandline argument or scripts = "custom" in the config file.
|
||||
//! Rustscan will look for the script configuration file in the user's home dir: home_dir/.rustscan_scripts.toml
|
||||
//! The config file have 3 optional fields, tag, developer and port. Just the tag field will be used forther in the process.
|
||||
//! RustScan will also look for available scripts in the user's home dir: home_dir/.rustscan_scripts
|
||||
//! and will try to read all the files, and parse them into a vector of ScriptFiles.
|
||||
//! Filtering on tags means the tags found in the rustscan_scripts.toml file will also have to be present in the Scriptfile,
|
||||
//! otherwise the script will not be selected.
|
||||
//! All of the rustscan_script.toml tags have to be present at minimum in a Scriptfile to get selected, but can be also more.
|
||||
//!
|
||||
//! Config file example:
|
||||
//! fixtures/test_rustscan_scripts.toml
|
||||
//!
|
||||
//! Script file examples:
|
||||
//! fixtures/test_script.py
|
||||
//! fixtures/test_script.pl
|
||||
//! fixtures/test_script.sh
|
||||
//! fixtures/test_script.txt
|
||||
//!
|
||||
//! call_format in script files can be of 2 variants.
|
||||
//! One is where all of the possible tags {{script}} {{ip}} {{port}} are there.
|
||||
//! The {{script}} part will be replaced with the scriptfile full path gathered while parsing available scripts.
|
||||
//! The {{ip}} part will be replaced with the ip we got from the scan.
|
||||
//! The {{port}} part will be reaplced with the ports separated with the ports_separator found in the script file
|
||||
//!
|
||||
//! And when there is only {{ip}} and {{port}} is in the format, ony those will be replaced with the arguments from the scan.
|
||||
//! This makes it easy to run a system installed command like nmap, and give any kind of arguments to it.
|
||||
//!
|
||||
//! If the format is different, the script will be silently discarded and will not run. With the Debug option it's possible to see where it goes wrong.
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use crate::input::ScriptsRequired;
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, prelude::*};
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
use subprocess::{Exec, ExitStatus};
|
||||
use text_placeholder::Template;
|
||||
|
||||
static DEFAULT: &str = r#"tags = ["core_approved", "RustScan", "default"]
|
||||
developer = [ "RustScan", "https://github.com/RustScan" ]
|
||||
ports_separator = ","
|
||||
call_format = "nmap -vvv -p {{port}} {{ip}}"
|
||||
"#;
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub fn init_scripts(scripts: ScriptsRequired) -> Result<Vec<ScriptFile>> {
|
||||
let mut scripts_to_run: Vec<ScriptFile> = Vec::new();
|
||||
|
||||
match scripts {
|
||||
ScriptsRequired::None => Ok(scripts_to_run),
|
||||
ScriptsRequired::Default => {
|
||||
let default_script =
|
||||
toml::from_str::<ScriptFile>(&DEFAULT).expect("Failed to parse Script file.");
|
||||
scripts_to_run.push(default_script);
|
||||
Ok(scripts_to_run)
|
||||
}
|
||||
ScriptsRequired::Custom => {
|
||||
let scripts_dir_base = match dirs::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => return Err(anyhow!("Could not infer scripts path.")),
|
||||
};
|
||||
let script_paths = match find_scripts(scripts_dir_base) {
|
||||
Ok(script_paths) => script_paths,
|
||||
Err(e) => return Err(anyhow!(e)),
|
||||
};
|
||||
debug!("Scripts paths \n{:?}", script_paths);
|
||||
|
||||
let parsed_scripts = parse_scripts(script_paths);
|
||||
debug!("Scripts parsed \n{:?}", parsed_scripts);
|
||||
|
||||
let script_config = match ScriptConfig::read_config() {
|
||||
Ok(script_config) => script_config,
|
||||
Err(e) => return Err(anyhow!(e)),
|
||||
};
|
||||
debug!("Script config \n{:?}", script_config);
|
||||
|
||||
// Only Scripts that contain all the tags found in ScriptConfig will be selected.
|
||||
if script_config.tags.is_some() {
|
||||
let config_hashset: HashSet<String> =
|
||||
script_config.tags.unwrap().into_iter().collect();
|
||||
for script in &parsed_scripts {
|
||||
if script.tags.is_some() {
|
||||
let script_hashset: HashSet<String> =
|
||||
script.tags.clone().unwrap().into_iter().collect();
|
||||
if config_hashset.is_subset(&script_hashset) {
|
||||
scripts_to_run.push(script.to_owned());
|
||||
} else {
|
||||
debug!(
|
||||
"\nScript tags does not match config tags {:?} {}",
|
||||
&script_hashset,
|
||||
script.path.clone().unwrap().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("\nScript(s) to run {:?}", scripts_to_run);
|
||||
Ok(scripts_to_run)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
if let Some(script_file) = ScriptFile::new(script) {
|
||||
parsed_scripts.push(script_file);
|
||||
}
|
||||
}
|
||||
parsed_scripts
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Script {
|
||||
// Path to the script itself.
|
||||
path: Option<PathBuf>,
|
||||
|
||||
// Ip got from scanner.
|
||||
ip: IpAddr,
|
||||
|
||||
// Ports found with portscan.
|
||||
open_ports: Vec<u16>,
|
||||
|
||||
// Port found in ScriptFile, if defined only this will run with the ip.
|
||||
trigger_port: Option<String>,
|
||||
|
||||
// Character to join ports in case we want to use a string format of them, for example nmap -p.
|
||||
ports_separator: Option<String>,
|
||||
|
||||
// Tags found in ScriptFile.
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
// The format how we want the script to run.
|
||||
call_format: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExecPartsScript {
|
||||
script: String,
|
||||
ip: String,
|
||||
port: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExecParts {
|
||||
ip: String,
|
||||
port: String,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
pub fn build(
|
||||
path: Option<PathBuf>,
|
||||
ip: IpAddr,
|
||||
open_ports: Vec<u16>,
|
||||
trigger_port: Option<String>,
|
||||
ports_separator: Option<String>,
|
||||
tags: Option<Vec<String>>,
|
||||
call_format: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
ip,
|
||||
open_ports,
|
||||
trigger_port,
|
||||
ports_separator,
|
||||
tags,
|
||||
call_format,
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
let separator = self.ports_separator.unwrap_or_else(|| ",".into());
|
||||
|
||||
let mut ports_str = self
|
||||
.open_ports
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(&separator);
|
||||
if let Some(port) = self.trigger_port {
|
||||
ports_str = port;
|
||||
}
|
||||
|
||||
let mut final_call_format = String::new();
|
||||
if let Some(call_format) = self.call_format {
|
||||
final_call_format = call_format;
|
||||
} else {
|
||||
return Err(anyhow!("Failed to parse execution format."));
|
||||
}
|
||||
let default_template: Template = Template::new(&final_call_format);
|
||||
let mut to_run = String::new();
|
||||
|
||||
if final_call_format.contains("{{script}}") {
|
||||
let exec_parts_script: ExecPartsScript = ExecPartsScript {
|
||||
script: self.path.unwrap().to_str().unwrap().to_string(),
|
||||
ip: self.ip.to_string(),
|
||||
port: ports_str,
|
||||
};
|
||||
to_run = default_template.fill_with_struct(&exec_parts_script)?;
|
||||
} else {
|
||||
let exec_parts: ExecParts = ExecParts {
|
||||
ip: self.ip.to_string(),
|
||||
port: ports_str,
|
||||
};
|
||||
to_run = default_template.fill_with_struct(&exec_parts)?;
|
||||
}
|
||||
debug!("\nScript format to run {}", to_run);
|
||||
|
||||
let arguments = shell_words::split(&to_run).expect("Failed to parse script arguments");
|
||||
|
||||
execute_script(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn execute_script(mut arguments: Vec<String>) -> Result<String> {
|
||||
debug!("\nScript arguments vec: {:?}", &arguments);
|
||||
let process = Exec::cmd(&arguments.remove(0)).args(&arguments);
|
||||
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(c.stdout_str())
|
||||
}
|
||||
Err(error) => {
|
||||
debug!("Command error {}", error.to_string());
|
||||
Err(anyhow!(error.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_scripts(mut path: PathBuf) -> Result<Vec<PathBuf>> {
|
||||
path.push(".rustscan_scripts");
|
||||
if path.is_dir() {
|
||||
debug!("Scripts folder found {}", &path.display());
|
||||
let mut files_vec: Vec<PathBuf> = Vec::new();
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
files_vec.push(entry.path());
|
||||
}
|
||||
Ok(files_vec)
|
||||
} else {
|
||||
Err(anyhow!("Can't find scripts folder {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ScriptFile {
|
||||
pub path: Option<PathBuf>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub developer: Option<Vec<String>>,
|
||||
pub port: Option<String>,
|
||||
pub ports_separator: Option<String>,
|
||||
pub call_format: Option<String>,
|
||||
}
|
||||
|
||||
impl ScriptFile {
|
||||
fn new(script: PathBuf) -> Option<ScriptFile> {
|
||||
let real_path = script.clone();
|
||||
let mut lines_buf = String::new();
|
||||
if let Ok(file) = File::open(script) {
|
||||
for line in io::BufReader::new(file).lines().skip(1) {
|
||||
if let Ok(mut line) = line {
|
||||
if line.starts_with('#') {
|
||||
line.retain(|c| c != '#');
|
||||
line = line.trim().to_string();
|
||||
line.push('\n');
|
||||
lines_buf.push_str(&line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Failed to read file: {}", &real_path.display());
|
||||
return None;
|
||||
}
|
||||
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);
|
||||
parsed.path = Some(real_path);
|
||||
// parsed_scripts.push(parsed);
|
||||
Some(parsed)
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to parse ScriptFile headers {}", e.to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ScriptConfig {
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub ports: Option<Vec<String>>,
|
||||
pub developer: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl ScriptConfig {
|
||||
pub fn read_config() -> Result<ScriptConfig> {
|
||||
let mut home_dir = match dirs::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => return Err(anyhow!("Could not infer ScriptConfig path.")),
|
||||
};
|
||||
home_dir.push(".rustscan_scripts.toml");
|
||||
|
||||
let content = fs::read_to_string(home_dir)?;
|
||||
let config = toml::from_str::<ScriptConfig>(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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
|
||||
fn into_script(script_f: ScriptFile) -> Script {
|
||||
Script::build(
|
||||
script_f.path,
|
||||
"127.0.0.1".parse().unwrap(),
|
||||
vec![80, 8080],
|
||||
script_f.port,
|
||||
script_f.ports_separator,
|
||||
script_f.tags,
|
||||
script_f.call_format,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_and_parse_scripts() {
|
||||
let scripts = find_scripts("fixtures/".into()).unwrap();
|
||||
let scripts = parse_scripts(scripts);
|
||||
assert_eq!(scripts.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn find_invalid_folder() {
|
||||
let _scripts = find_scripts("Cargo.toml".into()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn open_script_file_invalid_headers() {
|
||||
ScriptFile::new("fixtures/.rustscan_scripts/test_script_invalid_headers.txt".into())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn open_script_file_invalid_call_format() {
|
||||
let mut script_f =
|
||||
ScriptFile::new("fixtures/.rustscan_scripts/test_script.txt".into()).unwrap();
|
||||
script_f.call_format = Some("qwertyuiop".to_string());
|
||||
let script: Script = into_script(script_f);
|
||||
let _output = script.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn open_script_file_missing_call_format() {
|
||||
let mut script_f =
|
||||
ScriptFile::new("fixtures/.rustscan_scripts/test_script.txt".into()).unwrap();
|
||||
script_f.call_format = None;
|
||||
let script: Script = into_script(script_f);
|
||||
let _output = script.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn open_nonexisting_script_file() {
|
||||
ScriptFile::new("qwertyuiop.txt".into()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_txt_script() {
|
||||
let script_f =
|
||||
ScriptFile::new("fixtures/.rustscan_scripts/test_script.txt".into()).unwrap();
|
||||
assert_eq!(
|
||||
script_f.tags,
|
||||
Some(vec!["core_approved".to_string(), "example".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
script_f.developer,
|
||||
Some(vec![
|
||||
"example".to_string(),
|
||||
"https://example.org".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(script_f.ports_separator, Some(",".to_string()));
|
||||
assert_eq!(
|
||||
script_f.call_format,
|
||||
Some("nmap -vvv -p {{port}} {{ip}}".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_bash_script() {
|
||||
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.sh".into()).unwrap();
|
||||
let script: Script = into_script(script_f);
|
||||
let output = script.run().unwrap();
|
||||
// output has a newline at the end by default, .trim() trims it
|
||||
assert_eq!(output.trim(), "127.0.0.1 80,8080");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_python_script() {
|
||||
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.py".into()).unwrap();
|
||||
let script: Script = into_script(script_f);
|
||||
let output = script.run().unwrap();
|
||||
// output has a newline at the end by default, .trim() trims it
|
||||
assert_eq!(
|
||||
output.trim(),
|
||||
"Python script ran with arguments ['fixtures/.rustscan_scripts/test_script.py', '127.0.0.1', '80,8080']"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_perl_script() {
|
||||
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.pl".into()).unwrap();
|
||||
let script: Script = into_script(script_f);
|
||||
let output = script.run().unwrap();
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,16 @@ macro_rules! warning {
|
||||
use ansi_term::Colour::Red;
|
||||
println!("{} {}", Red.bold().paint("[!]"), $name);
|
||||
};
|
||||
// quiet mode
|
||||
($name:expr, $quiet:expr) => {
|
||||
($name:expr, $greppable:expr, $accessible:expr) => {
|
||||
use ansi_term::Colour::Red;
|
||||
if !$quiet {
|
||||
println!("{} {}", Red.bold().paint("[!]"), $name);
|
||||
// if not greppable then print, otherwise no else statement so do not print.
|
||||
if !$greppable {
|
||||
if $accessible {
|
||||
// Don't print the ascii art
|
||||
println!("{}", $name);
|
||||
} else {
|
||||
println!("{} {}", Red.bold().paint("[!]"), $name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -22,26 +27,38 @@ macro_rules! detail {
|
||||
use ansi_term::Colour::Blue;
|
||||
println!("{} {}", Blue.bold().paint("[~]"), $name);
|
||||
};
|
||||
($name:expr, $quiet:expr) => {
|
||||
($name:expr, $greppable:expr, $accessible:expr) => {
|
||||
use ansi_term::Colour::Blue;
|
||||
if !$quiet {
|
||||
println!("{} {}", Blue.bold().paint("[~]"), $name);
|
||||
// if not greppable then print, otherwise no else statement so do not print.
|
||||
if !$greppable {
|
||||
if $accessible {
|
||||
// Don't print the ascii art
|
||||
println!("{}", $name);
|
||||
} else {
|
||||
println!("{} {}", Blue.bold().paint("[~]"), $name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! output {
|
||||
($name:expr, $quiet:expr) => {
|
||||
use ansi_term::Colour::RGB;
|
||||
if !$quiet {
|
||||
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
|
||||
}
|
||||
};
|
||||
($name:expr) => {
|
||||
use ansi_term::Colour::RGB;
|
||||
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
|
||||
};
|
||||
($name:expr, $greppable:expr, $accessible:expr) => {
|
||||
use ansi_term::Colour::RGB;
|
||||
// if not greppable then print, otherwise no else statement so do not print.
|
||||
if !$greppable {
|
||||
if $accessible {
|
||||
// Don't print the ascii art
|
||||
println!("{}", $name);
|
||||
} else {
|
||||
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -55,6 +72,7 @@ macro_rules! funny_opening {
|
||||
"Real hackers hack time ⌛",
|
||||
"Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan",
|
||||
"😵 https://admin.tryhackme.com",
|
||||
"0day was here ♥",
|
||||
];
|
||||
let random_quote = quotes.choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Test rustscan against different targets with a time limit.
|
||||
* The tests assumes target/debug/rustscan has already been built.
|
||||
*
|
||||
* The tests are #[ignore] to avoid running them during normal development.
|
||||
*
|
||||
* Their tests in the timelimits module are run by travis during CI.
|
||||
*/
|
||||
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use wait_timeout::ChildExt;
|
||||
|
||||
const TIMEOUT_MARGIN: u32 = 3;
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn run_rustscan_with_timeout(args: &[&str], timeout: Duration) {
|
||||
println!("Running: target/debug/rustscan: {}", args.join(" "));
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let mut child = Command::new("target/debug/rustscan")
|
||||
.args(args)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let mut tries = TIMEOUT_MARGIN;
|
||||
loop {
|
||||
match child.wait_timeout(timeout).unwrap() {
|
||||
Some(_status) => break,
|
||||
None => {
|
||||
tries -= 1;
|
||||
if tries == 0 {
|
||||
// child hasn't exited yet
|
||||
child.kill().unwrap();
|
||||
child.wait().unwrap().code();
|
||||
panic!("Timeout while running command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = Instant::now();
|
||||
let duration = end.saturating_duration_since(start).as_secs_f32();
|
||||
|
||||
println!("time: {:1.1}s", duration);
|
||||
}
|
||||
|
||||
mod timelimits {
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn scan_localhost() {
|
||||
let timeout = super::Duration::from_secs(25);
|
||||
super::run_rustscan_with_timeout(&["--greppable", "--no-nmap", "127.0.0.1"], timeout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn scan_google_com() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--greppable",
|
||||
"--no-nmap",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"google.com",
|
||||
],
|
||||
super::Duration::from_secs(28),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn scan_example_com() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--greppable",
|
||||
"--no-nmap",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"example.com",
|
||||
],
|
||||
super::Duration::from_secs(28),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn scan_rustscan_cmnatic_co_uk() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--greppable",
|
||||
"--no-nmap",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"rustscan.cmnatic.co.uk",
|
||||
],
|
||||
super::Duration::from_secs(26),
|
||||
);
|
||||
}
|
||||
}
|
||||