Compare commits
317 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53e328d629 | ||
|
|
768f93755f | ||
|
|
9b16b67fb0 | ||
|
|
7aaad62ca2 | ||
|
|
49e2acab76 | ||
|
|
1f71836dbc | ||
|
|
cfe35bb710 | ||
|
|
77c5e3e115 | ||
|
|
44c708fe48 | ||
|
|
dd52fafae0 | ||
|
|
62cf78c34a | ||
|
|
c1a2d411b3 | ||
|
|
5554611f63 | ||
|
|
b49aba5a8a | ||
|
|
f9abd33c87 | ||
|
|
64d59c6eca | ||
|
|
ea94650802 | ||
|
|
fe588f3b1d | ||
|
|
88cb8b55f0 | ||
|
|
1eeb1508e5 | ||
|
|
c2f4edf475 | ||
|
|
ed55944e6e | ||
|
|
844a40e929 | ||
|
|
2238285c16 | ||
|
|
e397edb4f4 | ||
|
|
70b5039d50 | ||
|
|
326c868699 | ||
|
|
1be1967d6f | ||
|
|
5364328e2a | ||
|
|
01bcb10833 | ||
|
|
56f7b1c646 | ||
|
|
dc26c14da4 | ||
|
|
a2c70e43de | ||
|
|
b28276c42e | ||
|
|
aa3311bfab | ||
|
|
edb5b4bf25 | ||
|
|
231d3293fe | ||
|
|
febb39ab03 | ||
|
|
ebc1fa791d | ||
|
|
3ea88d9bd5 | ||
|
|
006e3618fb | ||
|
|
c4b74aa6fc | ||
|
|
6cd97d980d | ||
|
|
b4f2591378 | ||
|
|
3a204a92a5 | ||
|
|
b65c72d090 | ||
|
|
035ef9aa76 | ||
|
|
35c6501623 | ||
|
|
c0cb5840cc | ||
|
|
bba152d560 | ||
|
|
53a39d54bc | ||
|
|
455403baaf | ||
|
|
ec07344a0b | ||
|
|
c2d95659dc | ||
|
|
cd99ac3911 | ||
|
|
f2a5dbc60c | ||
|
|
6425483c2b | ||
|
|
25f940788f | ||
|
|
c232b23b1e | ||
|
|
5b19c8c4b3 | ||
|
|
aeba3f4574 | ||
|
|
1f5ea402ea | ||
|
|
d54ffd1eba | ||
|
|
5bca5677b6 | ||
|
|
af021cb6be | ||
|
|
52891cf6b1 | ||
|
|
6e76c035df | ||
|
|
850d628a93 | ||
|
|
5bb03d39bc | ||
|
|
f5660570d2 | ||
|
|
f1b0608daa | ||
|
|
c775ae1dee | ||
|
|
36b5219008 | ||
|
|
79982fd5f0 | ||
|
|
5fe71feec3 | ||
|
|
b8d4eb0f51 | ||
|
|
e6444db009 | ||
|
|
7dcb6b81dc | ||
|
|
4fb56d91c2 | ||
|
|
8c486b7e6e | ||
|
|
e939a28469 | ||
|
|
2dec9dd28c | ||
|
|
06459098b1 | ||
|
|
fc0447725c | ||
|
|
8312695e46 | ||
|
|
6c8e2df632 | ||
|
|
b1ee2d4ce7 | ||
|
|
4cbb72e4c8 | ||
|
|
9e76935f55 | ||
|
|
8b5ad635fa | ||
|
|
9f4914f419 | ||
|
|
f24b1b2b9b | ||
|
|
735e2dbaf8 | ||
|
|
5409ed8cdb | ||
|
|
d0a36be95a | ||
|
|
0ab8aace70 | ||
|
|
699ebad23a | ||
|
|
84e0e62753 | ||
|
|
dc8aac1b0f | ||
|
|
7b4d599951 | ||
|
|
d88c722a4b | ||
|
|
d70fc8f4c0 | ||
|
|
da08f3a4bb | ||
|
|
892fa0d3e4 | ||
|
|
605d691b28 | ||
|
|
efb458a725 | ||
|
|
84e147f709 | ||
|
|
1876e9ac8d | ||
|
|
8525883b91 | ||
|
|
02c537c253 | ||
|
|
d84038dcc3 | ||
|
|
becb44a5d7 | ||
|
|
6e1904eaf9 | ||
|
|
d493857011 | ||
|
|
3af9abde1b | ||
|
|
0ea8c46578 | ||
|
|
2e231da60c | ||
|
|
a97a7c6a4c | ||
|
|
2b09ca997c | ||
|
|
66fa77d16e | ||
|
|
23e06ede3b | ||
|
|
7b994d7cae | ||
|
|
59b591d0e8 | ||
|
|
309be4b22f | ||
|
|
02d0ccce64 | ||
|
|
a15d8167b9 | ||
|
|
d125d91f0d | ||
|
|
01e4f87420 | ||
|
|
037189ec57 | ||
|
|
0d77c06a95 | ||
|
|
247648aff8 | ||
|
|
f73c375d12 | ||
|
|
484507e033 | ||
|
|
27588f5319 | ||
|
|
c2b535eeed | ||
|
|
410a381643 | ||
|
|
f702f48708 | ||
|
|
031a269de0 | ||
|
|
c1b38c8fa6 | ||
|
|
afe302c101 | ||
|
|
b09c820a11 | ||
|
|
301ad3cf44 | ||
|
|
53b1e9fd1a | ||
|
|
5c447d259f | ||
|
|
67082a1002 | ||
|
|
152dd82848 | ||
|
|
21c70aaf58 | ||
|
|
a93d9ddbe2 | ||
|
|
1de7f1a2d2 | ||
|
|
4a82171ca1 | ||
|
|
1828b67509 | ||
|
|
8f2d71e631 | ||
|
|
2277089bda | ||
|
|
63f226f68f | ||
|
|
da0e6aa482 | ||
|
|
82a2bb1bd0 | ||
|
|
b96eea97ee | ||
|
|
fd420f4107 | ||
|
|
c5a23a5929 | ||
|
|
eb2e6203be | ||
|
|
b838dc5b31 | ||
|
|
983df4a12e | ||
|
|
9825bad1fe | ||
|
|
0183e5dbcf | ||
|
|
dacc28e2f1 | ||
|
|
90b3abc471 | ||
|
|
09333ebd0d | ||
|
|
e8b1b1ac87 | ||
|
|
eb56a66b20 | ||
|
|
fbd9909fef | ||
|
|
51e76b4c89 | ||
|
|
484a426834 | ||
|
|
31b8840f8c | ||
|
|
dfc13be9cc | ||
|
|
d6bb6a9a1f | ||
|
|
bfbe8f2c1a | ||
|
|
289b0edbb3 | ||
|
|
b69e5f879b | ||
|
|
d8810a449c | ||
|
|
bdcd052500 | ||
|
|
26aa17bf4d | ||
|
|
7a6d6cf2d5 | ||
|
|
e9cdc40821 | ||
|
|
c4c7b420ce | ||
|
|
3d304f13bc | ||
|
|
6ee61c189e | ||
|
|
5ad5666caf | ||
|
|
e057f8e6be | ||
|
|
baf44b4882 | ||
|
|
f7fda8de4c | ||
|
|
43154cf841 | ||
|
|
49f082875d | ||
|
|
1e575e0dbe | ||
|
|
d741020ff8 | ||
|
|
33dca47b38 | ||
|
|
33bd4f9e94 | ||
|
|
7a75a7a255 | ||
|
|
1c23995c86 | ||
|
|
23ae7491e3 | ||
|
|
487578f974 | ||
|
|
4dec06843f | ||
|
|
5f31289430 | ||
|
|
b519619324 | ||
|
|
5ff78297e4 | ||
|
|
c36e0e9a60 | ||
|
|
6e5a41fa34 | ||
|
|
896e13373e | ||
|
|
baeb200a1c | ||
|
|
6648a35f17 | ||
|
|
62204e2f31 | ||
|
|
98c1272874 | ||
|
|
27df923256 | ||
|
|
872d279c49 | ||
|
|
b548446759 | ||
|
|
d5ec02b3d7 | ||
|
|
b60de4547f | ||
|
|
7ab4cbd745 | ||
|
|
51fa7a02ae | ||
|
|
39bcc40f55 | ||
|
|
38c16d62ee | ||
|
|
ecd843c74f | ||
|
|
5611d54131 | ||
|
|
bd5aaaedcd | ||
|
|
c50b770b1e | ||
|
|
80f794b521 | ||
|
|
e22c346537 | ||
|
|
eed2da40b4 | ||
|
|
b5ba669d10 | ||
|
|
832a2608f4 | ||
|
|
ef4b3226ea | ||
|
|
54f2e60695 | ||
|
|
6f1125516c | ||
|
|
76042da044 | ||
|
|
193d855f69 | ||
|
|
65f282ac4c | ||
|
|
5fc97140f3 | ||
|
|
0ce8b70f09 | ||
|
|
b4fbca4e0d | ||
|
|
621bd9304c | ||
|
|
1ab5972f90 | ||
|
|
8aaa5bd167 | ||
|
|
4c89888a67 | ||
|
|
fc4d076113 | ||
|
|
798bd56e75 | ||
|
|
5359d1cb95 | ||
|
|
ff7fe3936b | ||
|
|
d1d221f81e | ||
|
|
e2acdf53a4 | ||
|
|
749d7efab5 | ||
|
|
d96a967fa9 | ||
|
|
3dddd0b041 | ||
|
|
33f02bb832 | ||
|
|
28a73e9399 | ||
|
|
040db1ecfe | ||
|
|
9212f5dbdd | ||
|
|
32c430c768 | ||
|
|
f3d72cf482 | ||
|
|
978df56ec4 | ||
|
|
699695e20f | ||
|
|
948a4a59cb | ||
|
|
dbb594d5da | ||
|
|
4f8a5da351 | ||
|
|
4d0d4fc992 | ||
|
|
31aa5cb6c0 | ||
|
|
a4c7894b9a | ||
|
|
51fcffe1c3 | ||
|
|
1fc3b8aa86 | ||
|
|
c93f19c02a | ||
|
|
2f7fbe199f | ||
|
|
5f3361828b | ||
|
|
910dd6f7c6 | ||
|
|
f7819d87c0 | ||
|
|
52ce2f9d6e | ||
|
|
91c73f88f6 | ||
|
|
419293ec00 | ||
|
|
361ea8a5d3 | ||
|
|
e0074faaad | ||
|
|
12fcfbd6e8 | ||
|
|
ba82a880ed | ||
|
|
87a3b0a683 | ||
|
|
48e0d4109a | ||
|
|
eba4da0e77 | ||
|
|
d42d1fe3bc | ||
|
|
e6fb92539a | ||
|
|
691a3b0350 | ||
|
|
e45a0289e9 | ||
|
|
8adfb8f4a1 | ||
|
|
76a71e1121 | ||
|
|
95b43a7855 | ||
|
|
84b7b1aade | ||
|
|
a11414b76c | ||
|
|
c80c7d3831 | ||
|
|
d0308e80c8 | ||
|
|
5183d1fea5 | ||
|
|
37a92566a1 | ||
|
|
2755a6968c | ||
|
|
ebcbb0bf55 | ||
|
|
d7e0282cea | ||
|
|
9ad4177828 | ||
|
|
d29f13830d | ||
|
|
af3e46da5a | ||
|
|
16054d4145 | ||
|
|
4d26c746ff | ||
|
|
dd9bc3c4fa | ||
|
|
43c95a779e | ||
|
|
12dd58ba43 | ||
|
|
6797187fc7 | ||
|
|
a3c5fb687e | ||
|
|
aab8a52861 | ||
|
|
bb1feaf9a7 | ||
|
|
f4a305ddd1 | ||
|
|
0d96f66cf9 | ||
|
|
a75b28effa | ||
|
|
1c147baafa | ||
|
|
1073464923 | ||
|
|
038e082ca2 | ||
|
|
aa296e2996 |
@@ -1,6 +1,7 @@
|
||||
target
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
docker
|
||||
docs
|
||||
ci
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
github: [kpcyrd]
|
||||
patreon: kpcyrd
|
||||
|
||||
58
.github/workflows/docker-release.yml
vendored
Normal file
58
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/kpcyrd/sn0int
|
||||
tags: |
|
||||
type=semver,pattern={{raw}}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
-
|
||||
name: Login to Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push Docker images
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
file: Dockerfile
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
-
|
||||
# Temp fix
|
||||
# https://github.com/docker/build-push-action/issues/252
|
||||
# https://github.com/moby/buildkit/issues/1896
|
||||
name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
30
.github/workflows/docker.yml
vendored
Normal file
30
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: sn0int
|
||||
file: Dockerfile
|
||||
- name: registry
|
||||
file: sn0int-registry/Dockerfile
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build the Docker image
|
||||
run: DOCKER_BUILDKIT=1 docker build -t ${{ matrix.build.name }} -f ${{ matrix.build.file }} .
|
||||
- name: Test the Docker image
|
||||
run: docker run --rm ${{ matrix.build.name }} --help
|
||||
|
||||
- name: Show Docker images
|
||||
run: docker images
|
||||
69
.github/workflows/rust.yml
vendored
Normal file
69
.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 9 * * 1'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies (apt)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install libsqlite3-dev libseccomp-dev libsodium-dev
|
||||
- name: Install dependencies (brew)
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install pkg-config libsodium
|
||||
|
||||
- name: Build (sn0int)
|
||||
run: cargo build --verbose
|
||||
- name: Build (common)
|
||||
run: cd sn0int-common && cargo build --verbose
|
||||
- name: Build (std)
|
||||
run: cd sn0int-std && cargo build --verbose
|
||||
- name: Build (examples)
|
||||
run: cargo build --verbose --examples
|
||||
|
||||
- name: Run tests (sn0int)
|
||||
run: cargo test --verbose
|
||||
- name: Run tests (sn0int, --ignored)
|
||||
run: cargo test --verbose -- --ignored
|
||||
- name: Run tests (common)
|
||||
run: cd sn0int-common && cargo test --verbose
|
||||
- name: Run tests (common, --ignored)
|
||||
run: cd sn0int-common && cargo test --verbose -- --ignored
|
||||
- name: Run tests (std)
|
||||
run: cd sn0int-std && cargo test --verbose
|
||||
- name: Run tests (std, --ignored)
|
||||
run: cd sn0int-std && cargo test --verbose -- --ignored
|
||||
|
||||
notify:
|
||||
# forks shouldn't notify
|
||||
if: github.repository == 'kpcyrd/sn0int'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
|
||||
steps:
|
||||
- name: irc notify
|
||||
uses: rectalogic/notify-irc@v1
|
||||
with:
|
||||
server: irc.hackint.org
|
||||
channel: "#sn0int"
|
||||
nickname: github-ci
|
||||
message: '${{ github.repository }}#${{ github.run_id }}(${{ github.event_name }}): ${{ github.ref }}: tests completed: ${{ needs.build.result }} (https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})'
|
||||
58
.travis.yml
58
.travis.yml
@@ -1,58 +0,0 @@
|
||||
language: rust
|
||||
|
||||
# upload takes too long on windows and gets killed due to inactivity
|
||||
#cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=test
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=common
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=boxxy
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=docker
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=docker-registry
|
||||
- os: osx
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=test
|
||||
- os: osx
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE=common
|
||||
#- os: windows
|
||||
# rust: stable
|
||||
# env:
|
||||
# - BUILD_MODE="windows test"
|
||||
- os: windows
|
||||
rust: stable
|
||||
env:
|
||||
- BUILD_MODE="windows common"
|
||||
|
||||
before_install:
|
||||
- ci/setup.sh "$TRAVIS_OS_NAME"
|
||||
script:
|
||||
- df -h
|
||||
- ci/run.sh $BUILD_MODE
|
||||
- df -h
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "ircs://irc.hackint.org:6697/#sn0int"
|
||||
#on_success: change # default: always
|
||||
#on_failure: always # default: always
|
||||
use_notice: true
|
||||
5697
Cargo.lock
generated
5697
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
119
Cargo.toml
119
Cargo.toml
@@ -1,107 +1,104 @@
|
||||
[package]
|
||||
name = "sn0int"
|
||||
version = "0.14.0"
|
||||
version = "0.26.1"
|
||||
description = "Semi-automatic OSINT framework and package manager"
|
||||
authors = ["kpcyrd <git@rxv.cc>"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/kpcyrd/sn0int"
|
||||
categories = ["command-line-utilities"]
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "kpcyrd/sn0int" }
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = ["sn0int-registry/sn0int-common",
|
||||
"sn0int-registry"]
|
||||
members = ["sn0int-common",
|
||||
"sn0int-registry",
|
||||
"sn0int-std"]
|
||||
|
||||
[package.metadata.deb]
|
||||
extended-description = """\
|
||||
sn0int (pronounced /snoɪnt/) is a semi-automatic OSINT framework and package
|
||||
manager. It was built for IT security professionals and bug hunters to gather
|
||||
intelligence about a given target or about yourself. sn0int is enumerating
|
||||
attack surface by semi-automatically processing public information and mapping
|
||||
the results in a unified format for followup investigations."""
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
depends = "$auto, publicsuffix"
|
||||
assets = [
|
||||
["target/release/sn0int", "usr/bin/", "755"],
|
||||
]
|
||||
|
||||
[features]
|
||||
sqlite-bundled = ["libsqlite3-sys/bundled"]
|
||||
|
||||
[dependencies]
|
||||
sn0int-common = { version="0.9.0", path="sn0int-registry/sn0int-common" }
|
||||
rustyline = "5.0"
|
||||
sn0int-common = { version="0.14.0", path="sn0int-common" }
|
||||
sn0int-std = { version="=0.26.0", path="sn0int-std" }
|
||||
rustyline = "10.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.7"
|
||||
env_logger = "0.11"
|
||||
hlua-badtouch = "0.4"
|
||||
structopt = "0.3"
|
||||
clap = { version = "4.3.11", features = ["derive", "env"] }
|
||||
clap_complete = "4.3.2"
|
||||
failure = "0.1"
|
||||
rand = "0.7"
|
||||
colored = "1.6"
|
||||
rand = "0.8"
|
||||
colored = "2"
|
||||
lazy_static = "1.0"
|
||||
shellwords = "1.0"
|
||||
publicsuffix = { version="1.5", default-features=false }
|
||||
diesel = { version = "1.0.0", features = ["sqlite", "chrono"] }
|
||||
diesel_migrations = { version = "1.3.0", features = ["sqlite"] }
|
||||
libsqlite3-sys = "0.16.0"
|
||||
libsqlite3-sys = { version = "0.22.0", features = ["bundled-windows"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dirs = "2.0"
|
||||
dirs-next = "2.0"
|
||||
url = "2.0"
|
||||
percent-encoding = "2.1"
|
||||
#chrootable-https = { path = "../chrootable-https" }
|
||||
chrootable-https = "0.13"
|
||||
rustls = { version="0.16", features=["dangerous_configuration"] }
|
||||
webpki = "0.21"
|
||||
webpki-roots = "0.18"
|
||||
pem = "0.7"
|
||||
base64 = "0.11"
|
||||
data-encoding = "2.1.2"
|
||||
kuchiki = "0.7.2"
|
||||
serde_urlencoded = "0.6"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
chrootable-https = "0.16"
|
||||
data-encoding = "2.3.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_urlencoded = "0.7"
|
||||
serde_json = "1.0"
|
||||
crossbeam-channel = "0.4"
|
||||
crossbeam-channel = "0.5"
|
||||
ctrlc = "3.1"
|
||||
opener = "0.4"
|
||||
opener = "0.6"
|
||||
separator = "0.4"
|
||||
maplit = "1.0.1"
|
||||
sloppy-rfc4880 = "0.1.5"
|
||||
sloppy-rfc4880 = "0.2"
|
||||
regex = "1.0"
|
||||
toml = "0.5"
|
||||
maxminddb = "0.13"
|
||||
tar = "0.4.17"
|
||||
libflate = "0.1.14"
|
||||
toml = "0.7"
|
||||
threadpool = "1.7"
|
||||
x509-parser = "0.6.0"
|
||||
der-parser = "3.0"
|
||||
atty = "0.2"
|
||||
bufstream = "0.1.4"
|
||||
tokio = "0.1.14"
|
||||
semver = "0.9"
|
||||
semver = "1"
|
||||
bytes = "0.4"
|
||||
xml-rs = "0.8"
|
||||
bytesize = "1.0"
|
||||
ipnetwork = "0.15"
|
||||
strum = "0.16"
|
||||
strum_macros = "0.16"
|
||||
ipnetwork = "0.20"
|
||||
strum = "0.25"
|
||||
strum_macros = "0.25"
|
||||
embedded-triple = "0.1.0"
|
||||
humansize = "2"
|
||||
|
||||
digest = "0.8.0"
|
||||
bs58 = "0.3"
|
||||
blake2 = "0.8.0"
|
||||
md-5 = "0.8.0"
|
||||
sha-1 = "0.8.1"
|
||||
sha2 = "0.8.0"
|
||||
sha3 = "0.8.0"
|
||||
hmac = "0.7"
|
||||
digest = "0.10"
|
||||
md-5 = "0.10"
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
sha3 = "0.10"
|
||||
hmac = "0.12"
|
||||
|
||||
image = "0.22"
|
||||
kamadak-exif = "0.3.1"
|
||||
walkdir = "2.2"
|
||||
nude = "0.2"
|
||||
nude = "0.3"
|
||||
glob = "0.3.0"
|
||||
os-version = "0.2"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
caps = "0.3"
|
||||
caps = "0.5"
|
||||
#syscallz = { path="../syscallz-rs" }
|
||||
syscallz = "0.11"
|
||||
nix = "0.15"
|
||||
syscallz = "0.16"
|
||||
nix = { version = "0.27", features = ["fs"] }
|
||||
|
||||
[target.'cfg(target_os="openbsd")'.dependencies]
|
||||
pledge = "0.3.1"
|
||||
unveil = "0.2.0"
|
||||
pledge = "0.4"
|
||||
unveil = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
#boxxy = { path = "../boxxy-rs" }
|
||||
boxxy = "0.11"
|
||||
boxxy = "0.13"
|
||||
tempfile = "3.0"
|
||||
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,15 +1,18 @@
|
||||
FROM rust:buster
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
FROM rust:alpine3.20
|
||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||
RUN apk add --no-cache musl-dev sqlite-dev libseccomp-dev libsodium-dev
|
||||
WORKDIR /usr/src/sn0int
|
||||
COPY . .
|
||||
RUN cargo build --release --verbose
|
||||
RUN strip target/release/sn0int
|
||||
RUN --mount=type=cache,target=/var/cache/buildkit \
|
||||
CARGO_HOME=/var/cache/buildkit/cargo \
|
||||
CARGO_TARGET_DIR=/var/cache/buildkit/target \
|
||||
cargo build --release --locked --verbose && \
|
||||
cp -v /var/cache/buildkit/target/release/sn0int /
|
||||
RUN strip /sn0int
|
||||
|
||||
FROM debian:buster
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=0 /usr/src/sn0int/target/release/sn0int /usr/local/bin/sn0int
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache libgcc sqlite-libs libseccomp libsodium
|
||||
COPY --from=0 /sn0int /usr/local/bin/sn0int
|
||||
VOLUME ["/data", "/cache"]
|
||||
ENV XDG_DATA_HOME=/data \
|
||||
XDG_CACHE_HOME=/cache
|
||||
|
||||
14
Makefile
14
Makefile
@@ -1,19 +1,23 @@
|
||||
check:
|
||||
(cd sn0int-registry/sn0int-common; cargo check)
|
||||
(cd sn0int-common; cargo check)
|
||||
(cd sn0int-registry; cargo check)
|
||||
(cd sn0int-std; cargo check)
|
||||
cargo check
|
||||
|
||||
force-check:
|
||||
(cd sn0int-registry/sn0int-common; touch src/lib.rs; cargo check)
|
||||
(cd sn0int-common; touch src/lib.rs; cargo check)
|
||||
(cd sn0int-registry; touch src/main.rs; cargo check)
|
||||
(cd sn0int-std; touch src/lib.rs; cargo check)
|
||||
touch src/lib.rs
|
||||
cargo check
|
||||
|
||||
test:
|
||||
(cd sn0int-registry/sn0int-common; cargo test)
|
||||
(cd sn0int-common; cargo test)
|
||||
(cd sn0int-registry; cargo test)
|
||||
cargo test
|
||||
cargo test -- --ignored
|
||||
(cd sn0int-std; cargo test)
|
||||
(cd sn0int-std; cargo test -- --ignored)
|
||||
cargo test --lib
|
||||
cargo test --lib -- --ignored
|
||||
|
||||
update:
|
||||
get-oui -v -u http://standards-oui.ieee.org/oui/oui.txt -f data/ieee-oui.txt
|
||||
|
||||
115
README.md
115
README.md
@@ -1,7 +1,5 @@
|
||||
# sn0int [![Build Status][travis-img]][travis] [![crates.io][crates-img]][crates] [![Documentation Status][docs-img]][docs] [![irc.hackint.org:6697/#sn0int][irc-img]][irc] [![@sn0int][twitter-img]][twitter] [![@sn0int@chaos.social][mastodon-img]][mastodon] [![registry status][registry-img]][registry]
|
||||
# sn0int [![crates.io][crates-img]][crates] [![Documentation Status][docs-img]][docs] [![irc.hackint.org:6697/#sn0int][irc-img]][irc] [![@sn0int][twitter-img]][twitter] [![@sn0int@chaos.social][mastodon-img]][mastodon] [![registry status][registry-img]][registry]
|
||||
|
||||
[travis-img]: https://travis-ci.org/kpcyrd/sn0int.svg?branch=master
|
||||
[travis]: https://travis-ci.org/kpcyrd/sn0int
|
||||
[crates-img]: https://img.shields.io/crates/v/sn0int.svg
|
||||
[crates]: https://crates.io/crates/sn0int
|
||||
[docs-img]: https://readthedocs.org/projects/sn0int/badge/?version=latest
|
||||
@@ -16,33 +14,34 @@
|
||||
[registry]: https://sn0int.com/
|
||||
|
||||
sn0int (pronounced [`/snoɪnt/`][ipa]) is a semi-automatic OSINT framework and
|
||||
package manager. It was built for IT security professionals and bug hunters to
|
||||
gather intelligence about a given target or about yourself. sn0int is
|
||||
enumerating attack surface by semi-automatically processing public information
|
||||
and mapping the results in a unified format for followup investigations.
|
||||
package manager. It's used by IT security professionals, bug bounty hunters,
|
||||
law enforcement agencies and in security awareness trainings to gather
|
||||
intelligence about a given target or about yourself. sn0int is enumerating
|
||||
attack surface by semi-automatically processing public information and mapping
|
||||
the results in a unified format for followup investigations.
|
||||
|
||||
[ipa]: http://ipa-reader.xyz/?text=sno%C9%AAnt
|
||||
|
||||
Among other things, sn0int is currently able to:
|
||||
|
||||
- Harvest subdomains from certificate transparency logs and passive dns
|
||||
- Mass resolve collected subdomains and scan for http or https services
|
||||
- Enrich ip addresses with asn and geoip info
|
||||
- Harvest emails from pgp keyservers and whois
|
||||
- Discover compromised logins in breaches
|
||||
- Find somebody's profiles across the internet
|
||||
- Enumerate local networks with unique techniques like passive arp
|
||||
- Gather information about phonenumbers
|
||||
- Attempt to bypass cloudflare with shodan
|
||||
- Harvest data and images from instagram profiles
|
||||
- Scan images for nudity
|
||||
- Harvest activity and images from social media profiles
|
||||
- Basic image processing
|
||||
|
||||
sn0int is heavily inspired by recon-ng and maltego, but remains more flexible
|
||||
and is fully opensource. None of the investigations listed above are hardcoded
|
||||
in the source, instead those are provided by modules that are executed in a
|
||||
in the source, instead they are provided by modules that are executed in a
|
||||
sandbox. You can easily extend sn0int by writing your own modules and share
|
||||
them with other users by publishing them to the sn0int registry. This allows
|
||||
you to ship updates for your modules on your own since you don't need to send a
|
||||
pull request.
|
||||
you to ship updates for your modules on your own instead of pull-requesting
|
||||
them into the sn0int codebase.
|
||||
|
||||
For questions and support join us on IRC: [irc.hackint.org:6697/#sn0int](https://webirc.hackint.org/#irc://irc.hackint.org/#sn0int)
|
||||
|
||||
@@ -50,6 +49,8 @@ For questions and support join us on IRC: [irc.hackint.org:6697/#sn0int](https:/
|
||||
|
||||
## Installation
|
||||
|
||||
<a href="https://repology.org/project/sn0int/versions"><img align="right" src="https://repology.org/badge/vertical-allrepos/sn0int.svg" alt="Packaging status"></a>
|
||||
|
||||
Archlinux
|
||||
|
||||
pacman -S sn0int
|
||||
@@ -58,6 +59,36 @@ Mac OSX
|
||||
|
||||
brew install sn0int
|
||||
|
||||
Debian/Ubuntu/Kali
|
||||
|
||||
There are prebuilt packages signed by a debian maintainer:
|
||||
|
||||
sudo apt install curl sq
|
||||
curl -sSf https://apt.vulns.sexy/kpcyrd.pgp | sq dearmor | sudo tee /etc/apt/trusted.gpg.d/apt-vulns-sexy.gpg > /dev/null
|
||||
echo deb http://apt.vulns.sexy stable main | sudo tee /etc/apt/sources.list.d/apt-vulns-sexy.list
|
||||
sudo apt update
|
||||
|
||||
Docker
|
||||
|
||||
docker run --rm --init -it -v "$PWD/.cache:/cache" -v "$PWD/.data:/data" ghcr.io/kpcyrd/sn0int
|
||||
|
||||
Alpine
|
||||
|
||||
apk add sn0int
|
||||
|
||||
OpenBSD
|
||||
|
||||
pkg_add sn0int
|
||||
|
||||
Gentoo
|
||||
|
||||
layman -a pentoo
|
||||
emerge --ask net-analyzer/sn0int
|
||||
|
||||
NixOS
|
||||
|
||||
nix-env -i sn0int
|
||||
|
||||
For everything else please have a look at the [detailed list][1].
|
||||
|
||||
[1]: https://sn0int.readthedocs.io/en/latest/install.html
|
||||
@@ -67,11 +98,14 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [Installation](https://sn0int.readthedocs.io/en/latest/install.html)
|
||||
- [Archlinux](https://sn0int.readthedocs.io/en/latest/install.html#archlinux)
|
||||
- [Mac OSX](https://sn0int.readthedocs.io/en/latest/install.html#mac-osx)
|
||||
- [Debian/Ubuntu/Kali](https://sn0int.readthedocs.io/en/latest/install.html#debian-ubuntu-kali)
|
||||
- [Debian >= bullseye, Ubuntu >= 20.04, Kali](https://sn0int.readthedocs.io/en/latest/install.html#debian-bullseye-ubuntu-20-04-kali)
|
||||
- [Debian <= buster, Ubuntu <= 19.10](https://sn0int.readthedocs.io/en/latest/install.html#debian-buster-ubuntu-19-10)
|
||||
- [Fedora/CentOS/Redhat](https://sn0int.readthedocs.io/en/latest/install.html#fedora-centos-redhat)
|
||||
- [Docker](https://sn0int.readthedocs.io/en/latest/install.html#docker)
|
||||
- [Alpine](https://sn0int.readthedocs.io/en/latest/install.html#alpine)
|
||||
- [OpenBSD](https://sn0int.readthedocs.io/en/latest/install.html#openbsd)
|
||||
- [Gentoo](https://sn0int.readthedocs.io/en/latest/install.html#gentoo)
|
||||
- [NixOS](https://sn0int.readthedocs.io/en/latest/install.html#nixos)
|
||||
- [Windows](https://sn0int.readthedocs.io/en/latest/install.html#windows)
|
||||
- [Build from source](https://sn0int.readthedocs.io/en/latest/build.html)
|
||||
- [Install dependencies](https://sn0int.readthedocs.io/en/latest/build.html#install-dependencies)
|
||||
@@ -89,10 +123,10 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [Running a module](https://sn0int.readthedocs.io/en/latest/usage.html#running-a-module)
|
||||
- [Running followup modules on the results](https://sn0int.readthedocs.io/en/latest/usage.html#running-followup-modules-on-the-results)
|
||||
- [Unscoping entities](https://sn0int.readthedocs.io/en/latest/usage.html#unscoping-entities)
|
||||
- [Autonoscope](https://sn0int.readthedocs.io/en/latest/usage.html#autonoscope)
|
||||
- [Domains](https://sn0int.readthedocs.io/en/latest/usage.html#domains)
|
||||
- [IPs](https://sn0int.readthedocs.io/en/latest/usage.html#ips)
|
||||
- [URLs](https://sn0int.readthedocs.io/en/latest/usage.html#urls)
|
||||
- [Autonoscope](https://sn0int.readthedocs.io/en/latest/autonoscope.html)
|
||||
- [Domains](https://sn0int.readthedocs.io/en/latest/autonoscope.html#domains)
|
||||
- [IPs](https://sn0int.readthedocs.io/en/latest/autonoscope.html#ips)
|
||||
- [URLs](https://sn0int.readthedocs.io/en/latest/autonoscope.html#urls)
|
||||
- [Writing your first module](https://sn0int.readthedocs.io/en/latest/scripting.html)
|
||||
- [Creating a repository](https://sn0int.readthedocs.io/en/latest/scripting.html#creating-a-repository)
|
||||
- [Publish your module](https://sn0int.readthedocs.io/en/latest/scripting.html#publish-your-module)
|
||||
@@ -100,6 +134,8 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [Reading data from stdin](https://sn0int.readthedocs.io/en/latest/scripting.html#reading-data-from-stdin)
|
||||
- [Database](https://sn0int.readthedocs.io/en/latest/database.html)
|
||||
- [db_add](https://sn0int.readthedocs.io/en/latest/database.html#db-add)
|
||||
- [db_add_ttl](https://sn0int.readthedocs.io/en/latest/database.html#db-add-ttl)
|
||||
- [db_activity](https://sn0int.readthedocs.io/en/latest/database.html#db-activity)
|
||||
- [db_update](https://sn0int.readthedocs.io/en/latest/database.html#db-update)
|
||||
- [db_select](https://sn0int.readthedocs.io/en/latest/database.html#db-select)
|
||||
- [Structs](https://sn0int.readthedocs.io/en/latest/structs.html)
|
||||
@@ -116,10 +152,29 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [Images](https://sn0int.readthedocs.io/en/latest/structs.html#images)
|
||||
- [Ports](https://sn0int.readthedocs.io/en/latest/structs.html#ports)
|
||||
- [Netblocks](https://sn0int.readthedocs.io/en/latest/structs.html#netblocks)
|
||||
- [CryptoAddrs](https://sn0int.readthedocs.io/en/latest/structs.html#cryptoaddrs)
|
||||
- [Activity](https://sn0int.readthedocs.io/en/latest/structs.html#activity)
|
||||
- [Relations](https://sn0int.readthedocs.io/en/latest/structs.html#relations)
|
||||
- [subdomain_ipaddr](https://sn0int.readthedocs.io/en/latest/structs.html#subdomain-ipaddr)
|
||||
- [network_device](https://sn0int.readthedocs.io/en/latest/structs.html#network-device)
|
||||
- [breach_email](https://sn0int.readthedocs.io/en/latest/structs.html#breach-email)
|
||||
- [Activity](https://sn0int.readthedocs.io/en/latest/activity.html)
|
||||
- [Anatomy of an event](https://sn0int.readthedocs.io/en/latest/activity.html#anatomy-of-an-event)
|
||||
- [Logging events](https://sn0int.readthedocs.io/en/latest/activity.html#logging-events)
|
||||
- [Querying events](https://sn0int.readthedocs.io/en/latest/activity.html#querying-events)
|
||||
- [Visualization](https://sn0int.readthedocs.io/en/latest/activity.html#visualization)
|
||||
- [Notifications](https://sn0int.readthedocs.io/en/latest/notifications.html)
|
||||
- [Receiving notifications](https://sn0int.readthedocs.io/en/latest/notifications.html#receiving-notifications)
|
||||
- [Telegram](https://sn0int.readthedocs.io/en/latest/notifications.html#telegram)
|
||||
- [Pushover](https://sn0int.readthedocs.io/en/latest/notifications.html#pushover)
|
||||
- [Discord](https://sn0int.readthedocs.io/en/latest/notifications.html#discord)
|
||||
- [Signal](https://sn0int.readthedocs.io/en/latest/notifications.html#signal)
|
||||
- [Writing your own module](https://sn0int.readthedocs.io/en/latest/notifications.html#writing-your-own-module)
|
||||
- [Setting up notification rules](https://sn0int.readthedocs.io/en/latest/notifications.html#setting-up-notification-rules)
|
||||
- [Testing notifications](https://sn0int.readthedocs.io/en/latest/notifications.html#testing-notifications)
|
||||
- [Running sn0int automatically](https://sn0int.readthedocs.io/en/latest/notifications.html#running-sn0int-automatically)
|
||||
- [Monitors](https://sn0int.readthedocs.io/en/latest/notifications.html#monitors)
|
||||
- [Timers](https://sn0int.readthedocs.io/en/latest/notifications.html#timers)
|
||||
- [Keyring](https://sn0int.readthedocs.io/en/latest/keyring.html)
|
||||
- [Managing the keyring](https://sn0int.readthedocs.io/en/latest/keyring.html#managing-the-keyring)
|
||||
- [Using access keys in scripts](https://sn0int.readthedocs.io/en/latest/keyring.html#using-access-keys-in-scripts)
|
||||
@@ -147,6 +202,7 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [datetime](https://sn0int.readthedocs.io/en/latest/reference.html#datetime)
|
||||
- [db_add](https://sn0int.readthedocs.io/en/latest/reference.html#db-add)
|
||||
- [db_add_ttl](https://sn0int.readthedocs.io/en/latest/reference.html#db-add-ttl)
|
||||
- [db_activity](https://sn0int.readthedocs.io/en/latest/reference.html#db-activity)
|
||||
- [db_select](https://sn0int.readthedocs.io/en/latest/reference.html#db-select)
|
||||
- [db_update](https://sn0int.readthedocs.io/en/latest/reference.html#db-update)
|
||||
- [dns](https://sn0int.readthedocs.io/en/latest/reference.html#dns)
|
||||
@@ -168,18 +224,28 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [http_fetch_json](https://sn0int.readthedocs.io/en/latest/reference.html#http-fetch-json)
|
||||
- [img_load](https://sn0int.readthedocs.io/en/latest/reference.html#img-load)
|
||||
- [img_exif](https://sn0int.readthedocs.io/en/latest/reference.html#img-exif)
|
||||
- [img_ahash](https://sn0int.readthedocs.io/en/latest/reference.html#img-ahash)
|
||||
- [img_dhash](https://sn0int.readthedocs.io/en/latest/reference.html#img-dhash)
|
||||
- [img_phash](https://sn0int.readthedocs.io/en/latest/reference.html#img-phash)
|
||||
- [img_nudity](https://sn0int.readthedocs.io/en/latest/reference.html#img-nudity)
|
||||
- [info](https://sn0int.readthedocs.io/en/latest/reference.html#info)
|
||||
- [intval](https://sn0int.readthedocs.io/en/latest/reference.html#intval)
|
||||
- [json_decode](https://sn0int.readthedocs.io/en/latest/reference.html#json-decode)
|
||||
- [json_decode_stream](https://sn0int.readthedocs.io/en/latest/reference.html#json-decode-stream)
|
||||
- [json_encode](https://sn0int.readthedocs.io/en/latest/reference.html#json-encode)
|
||||
- [key_trunc_pad](https://sn0int.readthedocs.io/en/latest/reference.html#key-trunc-pad)
|
||||
- [keyring](https://sn0int.readthedocs.io/en/latest/reference.html#keyring)
|
||||
- [last_err](https://sn0int.readthedocs.io/en/latest/reference.html#last-err)
|
||||
- [md5](https://sn0int.readthedocs.io/en/latest/reference.html#md5)
|
||||
- [mqtt_connect](https://sn0int.readthedocs.io/en/latest/reference.html#mqtt-connect)
|
||||
- [mqtt_subscribe](https://sn0int.readthedocs.io/en/latest/reference.html#mqtt-subscribe)
|
||||
- [mqtt_recv](https://sn0int.readthedocs.io/en/latest/reference.html#mqtt-recv)
|
||||
- [mqtt_ping](https://sn0int.readthedocs.io/en/latest/reference.html#mqtt-ping)
|
||||
- [pgp_pubkey](https://sn0int.readthedocs.io/en/latest/reference.html#pgp-pubkey)
|
||||
- [pgp_pubkey_armored](https://sn0int.readthedocs.io/en/latest/reference.html#pgp-pubkey-armored)
|
||||
- [print](https://sn0int.readthedocs.io/en/latest/reference.html#print)
|
||||
- [psl_domain_from_dns_name](https://sn0int.readthedocs.io/en/latest/reference.html#psl-domain-from-dns-name)
|
||||
- [ratelimit_throttle](https://sn0int.readthedocs.io/en/latest/reference.html#ratelimit-throttle)
|
||||
- [regex_find](https://sn0int.readthedocs.io/en/latest/reference.html#regex-find)
|
||||
- [regex_find_all](https://sn0int.readthedocs.io/en/latest/reference.html#regex-find-all)
|
||||
- [semver_match](https://sn0int.readthedocs.io/en/latest/reference.html#semver-match)
|
||||
@@ -195,6 +261,7 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [sn0int_version](https://sn0int.readthedocs.io/en/latest/reference.html#sn0int-version)
|
||||
- [sock_connect](https://sn0int.readthedocs.io/en/latest/reference.html#sock-connect)
|
||||
- [sock_upgrade_tls](https://sn0int.readthedocs.io/en/latest/reference.html#sock-upgrade-tls)
|
||||
- [sock_options](https://sn0int.readthedocs.io/en/latest/reference.html#sock-options)
|
||||
- [sock_send](https://sn0int.readthedocs.io/en/latest/reference.html#sock-send)
|
||||
- [sock_recv](https://sn0int.readthedocs.io/en/latest/reference.html#sock-recv)
|
||||
- [sock_sendline](https://sn0int.readthedocs.io/en/latest/reference.html#sock-sendline)
|
||||
@@ -206,11 +273,15 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [sock_recvuntil](https://sn0int.readthedocs.io/en/latest/reference.html#sock-recvuntil)
|
||||
- [sock_sendafter](https://sn0int.readthedocs.io/en/latest/reference.html#sock-sendafter)
|
||||
- [sock_newline](https://sn0int.readthedocs.io/en/latest/reference.html#sock-newline)
|
||||
- [sodium_secretbox_open](https://sn0int.readthedocs.io/en/latest/reference.html#sodium-secretbox-open)
|
||||
- [status](https://sn0int.readthedocs.io/en/latest/reference.html#status)
|
||||
- [stdin_readline](https://sn0int.readthedocs.io/en/latest/reference.html#stdin-readline)
|
||||
- [stdin_read_to_end](https://sn0int.readthedocs.io/en/latest/reference.html#stdin-read-to-end)
|
||||
- [str_find](https://sn0int.readthedocs.io/en/latest/reference.html#str-find)
|
||||
- [str_replace](https://sn0int.readthedocs.io/en/latest/reference.html#str-replace)
|
||||
- [strftime](https://sn0int.readthedocs.io/en/latest/reference.html#strftime)
|
||||
- [strptime](https://sn0int.readthedocs.io/en/latest/reference.html#strptime)
|
||||
- [strval](https://sn0int.readthedocs.io/en/latest/reference.html#strval)
|
||||
- [time_unix](https://sn0int.readthedocs.io/en/latest/reference.html#time-unix)
|
||||
- [url_decode](https://sn0int.readthedocs.io/en/latest/reference.html#url-decode)
|
||||
- [url_encode](https://sn0int.readthedocs.io/en/latest/reference.html#url-encode)
|
||||
@@ -221,6 +292,14 @@ For everything else please have a look at the [detailed list][1].
|
||||
- [utf8_decode](https://sn0int.readthedocs.io/en/latest/reference.html#utf8-decode)
|
||||
- [warn](https://sn0int.readthedocs.io/en/latest/reference.html#warn)
|
||||
- [warn_once](https://sn0int.readthedocs.io/en/latest/reference.html#warn-once)
|
||||
- [ws_connect](https://sn0int.readthedocs.io/en/latest/reference.html#ws-connect)
|
||||
- [ws_options](https://sn0int.readthedocs.io/en/latest/reference.html#ws-options)
|
||||
- [ws_recv_text](https://sn0int.readthedocs.io/en/latest/reference.html#ws-recv-text)
|
||||
- [ws_recv_binary](https://sn0int.readthedocs.io/en/latest/reference.html#ws-recv-binary)
|
||||
- [ws_recv_json](https://sn0int.readthedocs.io/en/latest/reference.html#ws-recv-json)
|
||||
- [ws_send_text](https://sn0int.readthedocs.io/en/latest/reference.html#ws-send-text)
|
||||
- [ws_send_binary](https://sn0int.readthedocs.io/en/latest/reference.html#ws-send-binary)
|
||||
- [ws_send_json](https://sn0int.readthedocs.io/en/latest/reference.html#ws-send-json)
|
||||
- [x509_parse_pem](https://sn0int.readthedocs.io/en/latest/reference.html#x509-parse-pem)
|
||||
- [xml_decode](https://sn0int.readthedocs.io/en/latest/reference.html#xml-decode)
|
||||
- [xml_named](https://sn0int.readthedocs.io/en/latest/reference.html#xml-named)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
from subprocess import DEVNULL, PIPE
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import json
|
||||
import sys
|
||||
@@ -47,10 +49,10 @@ def main(tempdir, binary):
|
||||
|
||||
print('[*] installing modules')
|
||||
sn0int(tempdir, binary, [
|
||||
'mod install kpcyrd/ctlogs',
|
||||
'mod install kpcyrd/dns-resolve',
|
||||
'mod install kpcyrd/url-scan',
|
||||
'mod install kpcyrd/geoip',
|
||||
'pkg install kpcyrd/ctlogs',
|
||||
'pkg install kpcyrd/dns-resolve',
|
||||
'pkg install kpcyrd/url-scan',
|
||||
'pkg install kpcyrd/geoip',
|
||||
])
|
||||
|
||||
print('[*] running ctlogs')
|
||||
@@ -63,6 +65,7 @@ def main(tempdir, binary):
|
||||
print('[*] testing db for subdomains')
|
||||
subdomains = sn0int_select(tempdir, binary, ['subdomains'])
|
||||
assert {x['value'] for x in subdomains} == {
|
||||
'example.com',
|
||||
'www.example.com',
|
||||
'm.example.com',
|
||||
'dev.example.com',
|
||||
@@ -91,10 +94,17 @@ def main(tempdir, binary):
|
||||
print('[*] testing db for urls')
|
||||
urls = sn0int_select(tempdir, binary, ['urls'])
|
||||
assert {(x['value'], x['status']) for x in urls} == {
|
||||
('http://example.com/', 200),
|
||||
('https://example.com/', 200),
|
||||
('http://www.example.com/', 200),
|
||||
('https://www.example.com/', 200),
|
||||
}
|
||||
|
||||
cache = Path.home() / '.cache' / 'sn0int'
|
||||
if cache.exists():
|
||||
print('[*] copying geoip files')
|
||||
shutil.copytree(cache, tempdir + '/.cache/sn0int', dirs_exist_ok=True)
|
||||
|
||||
print('[*] running geoip')
|
||||
sn0int(tempdir, binary, [
|
||||
'use geoip',
|
||||
|
||||
41
ci/run.sh
41
ci/run.sh
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -exu
|
||||
case "$1" in
|
||||
build)
|
||||
cargo build --verbose
|
||||
cargo build --verbose --examples
|
||||
;;
|
||||
test)
|
||||
ci/run.sh build
|
||||
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz \
|
||||
https://geolite.maxmind.com/download/geoip/database/GeoLite2-ASN.tar.gz
|
||||
cargo run --example maxmind -- dl -e GeoLite2-City.tar.gz GeoLite2-City.mmdb GeoLite2-City.mmdb
|
||||
cargo run --example maxmind -- dl -e GeoLite2-ASN.tar.gz GeoLite2-ASN.mmdb GeoLite2-ASN.mmdb
|
||||
cargo test --verbose
|
||||
cargo test --verbose -- --ignored
|
||||
;;
|
||||
common)
|
||||
cd sn0int-registry/sn0int-common
|
||||
cargo test --verbose
|
||||
;;
|
||||
windows)
|
||||
cargo build --verbose --features=sqlite-bundled
|
||||
cargo build --verbose --examples --features=sqlite-bundled
|
||||
;;
|
||||
boxxy)
|
||||
cargo build --verbose --examples
|
||||
if cat ci/boxxy_stage1.txt | RUST_LOG=boxxy cargo run --example boxxy; then
|
||||
echo "SANDOX ERROR: should've crashed"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
docker)
|
||||
docker build -t sn0int .
|
||||
docker images
|
||||
docker run --rm sn0int --help
|
||||
;;
|
||||
docker-registry)
|
||||
docker build -t sn0int-registry sn0int-registry/
|
||||
docker images
|
||||
;;
|
||||
esac
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -exu
|
||||
case "$1" in
|
||||
linux)
|
||||
sudo apt update
|
||||
sudo apt install libsqlite3-dev libseccomp-dev
|
||||
;;
|
||||
esac
|
||||
@@ -1,13 +1,13 @@
|
||||
FROM alpine:edge
|
||||
RUN apk add --no-cache sqlite-dev libseccomp-dev
|
||||
RUN apk add --no-cache --virtual .build-rust rust cargo
|
||||
FROM rust:alpine3.11
|
||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||
RUN apk add --no-cache musl-dev sqlite-dev libseccomp-dev libsodium-dev
|
||||
WORKDIR /usr/src/sn0int
|
||||
COPY . .
|
||||
RUN cargo build --release --verbose
|
||||
RUN strip target/release/sn0int
|
||||
|
||||
FROM alpine:edge
|
||||
RUN apk add --no-cache libgcc sqlite-libs libseccomp
|
||||
FROM alpine:3.11
|
||||
RUN apk add --no-cache libgcc sqlite-libs libseccomp libsodium
|
||||
COPY --from=0 /usr/src/sn0int/target/release/sn0int /usr/local/bin/sn0int
|
||||
VOLUME ["/data", "/cache"]
|
||||
ENV XDG_DATA_HOME=/data \
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
FROM rust
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev \
|
||||
FROM rust:buster
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev libsodium-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /usr/src/sn0int
|
||||
COPY . .
|
||||
RUN cargo build --release --verbose
|
||||
RUN strip target/release/sn0int
|
||||
|
||||
FROM debian
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev \
|
||||
FROM debian:buster
|
||||
RUN apt-get update -q && apt-get install -yq libsqlite3-dev libseccomp-dev libsodium-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=0 /usr/src/sn0int/target/release/sn0int /usr/local/bin/sn0int
|
||||
VOLUME ["/data", "/cache"]
|
||||
|
||||
166
docs/activity.rst
Normal file
166
docs/activity.rst
Normal file
@@ -0,0 +1,166 @@
|
||||
Activity
|
||||
========
|
||||
|
||||
So far we've learned about regular `structs <structs.html>`_, but activity is
|
||||
special.
|
||||
|
||||
Activity is an event tied to a specific time and topic and has a small amount
|
||||
of data piggybacked to it.
|
||||
|
||||
Anatomy of an event
|
||||
-------------------
|
||||
|
||||
``topic``
|
||||
This is some freestyle text used to group events to a specific topic. This
|
||||
must not conflict with other modules unless there's a very good reason.
|
||||
|
||||
The topic should look like ``kpcyrd/example:something``, with ``something``
|
||||
being a meaningful unique identifier for whatever is generating these
|
||||
events, like a mac address or an account name/id.
|
||||
|
||||
The rules around this might become stricter in the future.
|
||||
``time``
|
||||
The most important part of the event: The time and date it happened.
|
||||
``initial``
|
||||
This value can not be set but might be present in sn0int output. See `Querying events`_.
|
||||
``uniq`` (optional)
|
||||
This is an optional feature to deduplicate events. Assuming you're
|
||||
importing posts by an account, you wouldn't want to store a new event for
|
||||
each post you already imported. If you set this field to the technical post
|
||||
id then sn0int would skip the event if it already has an event with the
|
||||
same ``topic`` and ``uniq`` combination to avoid inserting duplicates.
|
||||
``latitude`` (optional)
|
||||
Latitude - if you can tie the event to a specific location.
|
||||
``longitude`` (optional)
|
||||
Longitude - if you can tie the event to a specific location.
|
||||
``radius`` (optional)
|
||||
The location radius in meters. If the position you got has a precision of
|
||||
100 meters set this value to ``100``.
|
||||
``content``
|
||||
Arbitrary data that you want to attach to the event. This doesn't need to
|
||||
be a string and can be an arbitrary object that is then stored as json
|
||||
string.
|
||||
|
||||
Logging events
|
||||
--------------
|
||||
|
||||
An ``activity`` event can be logged with ``db_activity``:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
content={
|
||||
a='b',
|
||||
foo={
|
||||
bar=1337,
|
||||
},
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
|
||||
Logging an event that has a location attached could look like this:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
latitude=40.726662,
|
||||
longitude=-74.036677,
|
||||
radius=50,
|
||||
content={
|
||||
a='b',
|
||||
foo={
|
||||
bar=1337,
|
||||
},
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
|
||||
Making sure an event is not logged twice can be done with ``uniq``:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- create the first event
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
uniq='12345',
|
||||
content='ohai',
|
||||
})
|
||||
|
||||
-- this does nothing because we already have an event with this topic+uniq combination
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
uniq='12345',
|
||||
content='ohai',
|
||||
})
|
||||
|
||||
-- this creates a new event because uniq is different
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
uniq='6789',
|
||||
content='ohai',
|
||||
})
|
||||
|
||||
-- this also creates a new event because topic is different
|
||||
db_activity({
|
||||
topic='harness/activity-ping:something-else',
|
||||
time=sn0int_time(),
|
||||
uniq='6789',
|
||||
content='ohai',
|
||||
})
|
||||
|
||||
Querying events
|
||||
---------------
|
||||
|
||||
There is a commandline interface that can be used to query all events we've
|
||||
logged. To get everything (sorted by time)::
|
||||
|
||||
sn0int activity
|
||||
|
||||
To limit the output to a specific topic::
|
||||
|
||||
sn0int activity -t harness/activity-ping:dummy
|
||||
|
||||
To limit it to a specific time frame::
|
||||
|
||||
# everything since
|
||||
sn0int activity --since 2020-01-13T04:20:00
|
||||
# everything until
|
||||
sn0int activity --until 2020-01-13T04:20:00
|
||||
# both
|
||||
sn0int activity --since yesterday --until today
|
||||
|
||||
When using ``--since`` you might also want to know the previous state and use
|
||||
it as an initial value. Consider this example::
|
||||
|
||||
2020-01-13 14:30:00 # user goes offline
|
||||
2020-01-13 23:59:00 # user goes online
|
||||
2020-01-14 09:30:00 # user goes idle
|
||||
2020-01-14 14:20:00 # user goes offline
|
||||
|
||||
If we're running a query like ``sn0int activity --since 2020-01-14T00:00:00``
|
||||
the program consuming the output wouldn't know that the user is initially
|
||||
online because we're only getting this data::
|
||||
|
||||
{"id":8,"topic":"foo/bar:asdf","time":"2020-01-14T09:30:00","content":{"state":"idle"}}
|
||||
{"id":9,"topic":"foo/bar:asdf","time":"2020-01-14T14:20:00","content":{"state":"offline"}}
|
||||
|
||||
We can tweak this with ``sn0int activity --initial --since
|
||||
2020-01-14T00:00:00`` to include one more event that we only use to populate
|
||||
the intial state::
|
||||
|
||||
{"id":7,"initial":true,"topic":"foo/bar:asdf","time":"2020-01-13T23:59:00","content":{"state":"online"}}
|
||||
{"id":8,"topic":"foo/bar:asdf","time":"2020-01-14T09:30:00","content":{"state":"idle"}}
|
||||
{"id":9,"topic":"foo/bar:asdf","time":"2020-01-14T14:20:00","content":{"state":"offline"}}
|
||||
|
||||
Visualization
|
||||
-------------
|
||||
|
||||
There is no visualization built in, there may be external frontends for this in
|
||||
the future. You're very welcome to write one!
|
||||
81
docs/autonoscope.rst
Normal file
81
docs/autonoscope.rst
Normal file
@@ -0,0 +1,81 @@
|
||||
Autonoscope
|
||||
===========
|
||||
|
||||
Instead of manually unscoping everything you can also define so called
|
||||
autonoscope rules. Those are executed from most specific to least specific and
|
||||
the first match wins. If no rule matches, the default is in-scope::
|
||||
|
||||
[sn0int][demo] > # add the domain first
|
||||
[sn0int][demo] > # this is necessary because we only want to partially unscope example.com
|
||||
[sn0int][demo] > add domain example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > # automatically noscope all subdomains
|
||||
[sn0int][demo] > autonoscope add domain example.com
|
||||
[sn0int][demo] > # except subdomains of prod.example.com
|
||||
[sn0int][demo] > autoscope add domain prod.example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > autonoscope list
|
||||
scope domain "prod.example.com"
|
||||
noscope domain "example.com"
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > # this is going to be out-of-scope
|
||||
[sn0int][demo] > add subdomain www.example.com
|
||||
[sn0int][demo] > # this is going to be in-scope
|
||||
[sn0int][demo] > add subdomain db.prod.example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > select subdomains
|
||||
#1, "www.example.com"
|
||||
#2, "db.prod.example.com"
|
||||
[sn0int][demo] > select subdomains where unscoped=0
|
||||
#2, "db.prod.example.com"
|
||||
[sn0int][demo] > select subdomains where unscoped=1
|
||||
#1, "www.example.com"
|
||||
[sn0int][demo] >
|
||||
|
||||
Domains
|
||||
-------
|
||||
|
||||
Autonoscope rules for domains are applied to the following structs:
|
||||
|
||||
- domains
|
||||
- subdomains
|
||||
- urls
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add domain example.com
|
||||
autonoscope add domain staging.example.com
|
||||
autonoscope add domain com
|
||||
autonoscope add domain .
|
||||
|
||||
IPs
|
||||
---
|
||||
|
||||
Autonoscope rules for IPs are applied to the following structs:
|
||||
|
||||
- ipaddrs
|
||||
- netblocks
|
||||
- ports
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add ip 0.0.0.0/0
|
||||
autonoscope add ip ::/0
|
||||
autonoscope add ip 192.168.0.0/16
|
||||
autonoscope add ip 10.13.33.37/32
|
||||
|
||||
URLs
|
||||
----
|
||||
|
||||
Autonoscope rules for urls are applied to the following structs:
|
||||
|
||||
- urls
|
||||
|
||||
Note that these rules are specific to a certain origin (like
|
||||
``https://example.com``) and are used to filter paths.
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add url https://example.com/
|
||||
autonoscope add url https://example.com/admin/
|
||||
autonoscope add url https://example.com/a/b/c/d
|
||||
@@ -18,19 +18,21 @@ Archlinux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pacman -S geoip2-database libseccomp publicsuffix-list sqlite
|
||||
$ pacman -S geoip2-database libseccomp libsodium publicsuffix-list sqlite
|
||||
|
||||
Mac OSX
|
||||
~~~~~~~
|
||||
|
||||
None.
|
||||
.. code-block:: bash
|
||||
|
||||
$ brew install libsodium
|
||||
|
||||
Debian/Ubuntu/Kali
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt install build-essential libsqlite3-dev libseccomp-dev publicsuffix
|
||||
$ apt install build-essential libsqlite3-dev libseccomp-dev libsodium-dev publicsuffix pkg-config
|
||||
|
||||
.. warning::
|
||||
On a debian based system make sure you've installed rust with rustup.
|
||||
@@ -40,21 +42,28 @@ Alpine
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apk add sqlite-dev libseccomp-dev
|
||||
$ apk add sqlite-dev libseccomp-dev libsodium-dev
|
||||
|
||||
Docker
|
||||
~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ DOCKER_BUILDKIT=1 docker build -t kpcyrd/sn0int .
|
||||
|
||||
OpenBSD
|
||||
~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pkg_add sqlite3 geolite2-city geolite2-asn
|
||||
$ pkg_add sqlite3 geolite2-city geolite2-asn libsodium
|
||||
|
||||
Gentoo
|
||||
~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
emerge --ask sys-libs/libseccomp dev-db/sqlite
|
||||
emerge --ask sys-libs/libseccomp dev-db/sqlite dev-libs/libsodium
|
||||
|
||||
Windows
|
||||
~~~~~~~
|
||||
@@ -71,10 +80,4 @@ After all dependencies have been installed, simply build the binary:
|
||||
|
||||
$ cargo build --release
|
||||
|
||||
Note that you need a different command on windows:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ cargo build --release --features=sqlite-bundled
|
||||
|
||||
After the build finished the binary is located at ``target/release/sn0int``.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'sn0int'
|
||||
copyright = '2018, kpcyrd'
|
||||
copyright = '2018-2020, kpcyrd'
|
||||
author = 'kpcyrd'
|
||||
|
||||
# The short X.Y version
|
||||
|
||||
@@ -46,6 +46,45 @@ triggered and an db_update is performed instead.
|
||||
removed from scope with ``noscope``. Everytime you use ``db_add`` you need
|
||||
to make sure that the ID that has been returned is not ``nil``.
|
||||
|
||||
db_add_ttl
|
||||
----------
|
||||
|
||||
Add a temporary entity to the database. This is commonly used to insert
|
||||
temporary links that automatically expire over time. If the entity already
|
||||
exists and is also marked as temporary the new ttl is going to replace the old
|
||||
ttl. If the entity already exists but never expires we are not going to add a
|
||||
ttl.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- this link is valid for 2min
|
||||
domain_id = db_add_ttl('network-device', {
|
||||
network_id=1,
|
||||
device_id=13,
|
||||
}, 120)
|
||||
|
||||
db_activity
|
||||
-----------
|
||||
|
||||
Log an activity event. A basic event looks like this:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
content={
|
||||
a='b',
|
||||
foo={
|
||||
bar=1337,
|
||||
},
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
|
||||
This function is explained in detail in the `activity <activity.html>`_
|
||||
section.
|
||||
|
||||
db_update
|
||||
---------
|
||||
|
||||
|
||||
@@ -38,9 +38,12 @@ Getting Started
|
||||
install
|
||||
build
|
||||
usage
|
||||
autonoscope
|
||||
scripting
|
||||
database
|
||||
structs
|
||||
activity
|
||||
notifications
|
||||
keyring
|
||||
config
|
||||
sandbox
|
||||
|
||||
@@ -19,12 +19,34 @@ Mac OSX
|
||||
|
||||
$ brew install sn0int
|
||||
|
||||
Debian/Ubuntu/Kali
|
||||
------------------
|
||||
Debian >= bookwork, Ubuntu >= 22.10, Kali
|
||||
-----------------------------------------
|
||||
|
||||
Note that debian `doesn't ship the geoip2-database
|
||||
<https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=757723>`_ so we're going to
|
||||
download them automatically during the first run.
|
||||
There are prebuilt packages signed by a debian maintainer:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo apt install curl sq
|
||||
$ curl -sSf https://apt.vulns.sexy/kpcyrd.pgp | sq keyring filter -B --handle 64B13F7117D6E07D661BBCE0FE763A64F5E54FD6 | sudo tee /etc/apt/trusted.gpg.d/apt-vulns-sexy.gpg > /dev/null
|
||||
$ echo deb http://apt.vulns.sexy stable main | sudo tee /etc/apt/sources.list.d/apt-vulns-sexy.list
|
||||
$ apt update
|
||||
$ apt install sn0int
|
||||
|
||||
Debian <= bullseye, Ubuntu <= 22.04
|
||||
-----------------------------------
|
||||
|
||||
There are prebuilt packages signed by a debian maintainer:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo apt install curl sq
|
||||
$ curl -sSf https://apt.vulns.sexy/kpcyrd.pgp | sq dearmor | sudo tee /etc/apt/trusted.gpg.d/apt-vulns-sexy.gpg > /dev/null
|
||||
$ echo deb http://apt.vulns.sexy stable main | sudo tee /etc/apt/sources.list.d/apt-vulns-sexy.list
|
||||
$ apt update
|
||||
$ apt install sn0int
|
||||
|
||||
Fedora/CentOS/Redhat
|
||||
--------------------
|
||||
|
||||
Using rust+cargo from the repos might work for you, but we only officially
|
||||
support rust+cargo installed with `rustup <https://rustup.rs/>`_. Have a look
|
||||
@@ -32,7 +54,7 @@ at the docker image as an alternative.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt install build-essential libsqlite3-dev libseccomp-dev publicsuffix
|
||||
$ dnf install @development-tools libsq3-devel libseccomp-devel libsodium-devel publicsuffix-list
|
||||
$ git clone https://github.com/kpcyrd/sn0int.git
|
||||
$ cd sn0int
|
||||
$ cargo install -f --path .
|
||||
@@ -47,8 +69,6 @@ Docker
|
||||
Alpine
|
||||
------
|
||||
|
||||
On alpine edge, with enabled testing repositories:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apk add sn0int
|
||||
@@ -56,8 +76,6 @@ On alpine edge, with enabled testing repositories:
|
||||
OpenBSD
|
||||
-------
|
||||
|
||||
On -current:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pkg_add sn0int
|
||||
@@ -67,8 +85,15 @@ Gentoo
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
layman -f -o https://raw.githubusercontent.com/kpcyrd/overlay/master/overlay.xml -a kpcyrd-overlay
|
||||
emerge --ask net-analyzer/sn0int
|
||||
$ layman -a pentoo
|
||||
$ emerge --ask net-analyzer/sn0int
|
||||
|
||||
NixOS
|
||||
-----
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ nix-env -i sn0int
|
||||
|
||||
Windows
|
||||
-------
|
||||
@@ -82,4 +107,4 @@ Make sure rust is installed and setup.
|
||||
|
||||
$ git clone https://github.com/kpcyrd/sn0int.git
|
||||
$ cd sn0int
|
||||
$ cargo install -f --path . --features=sqlite-bundled
|
||||
$ cargo install -f --path .
|
||||
|
||||
@@ -58,6 +58,10 @@ If the user granted us access to those keys we can read them with ``keyring``:
|
||||
This returns a list of all keys in that namespace. Any empty list is returned
|
||||
if the user doesn't have any keys in that namespace.
|
||||
|
||||
If you want to allow the user to select a specific script you can introduce an
|
||||
option that is set by the user and then filter ``creds`` until the
|
||||
``access_key`` matches.
|
||||
|
||||
Using access keys as source argument
|
||||
------------------------------------
|
||||
|
||||
|
||||
311
docs/notifications.rst
Normal file
311
docs/notifications.rst
Normal file
@@ -0,0 +1,311 @@
|
||||
Notifications
|
||||
=============
|
||||
|
||||
If you run sn0int unattended nobody might see the sn0int output. For cases like
|
||||
this you can configure notifications to send you a push notification in case
|
||||
something interesting happens. This is also especially useful if you have
|
||||
sn0int setup to run automatically.
|
||||
|
||||
Receiving notifications
|
||||
-----------------------
|
||||
|
||||
Notifications are just regular sn0int modules. You can install them just like
|
||||
any other module or write your own. This section contains walkthroughs on how
|
||||
to setup common integrations.
|
||||
|
||||
Telegram
|
||||
~~~~~~~~
|
||||
|
||||
Install the telegram notification module from the registry:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int pkg install kpcyrd/notify-telegram
|
||||
|
||||
Open your telegram app and open a chat with ``@botfather``. Send ``/newbot``
|
||||
and answer the questions. Copy ``bot_token`` and open this url in your browser:
|
||||
|
||||
.. code-block::
|
||||
|
||||
https://api.telegram.org/bot**your_bot_token**/getUpdates
|
||||
|
||||
Back on your app, open the t.me link to start a new chat with your bot, then
|
||||
send ``/start``. Reload the page in your browser, you should see the new
|
||||
message you sent. Copy the ``chat_id``.
|
||||
|
||||
Test your tokens are working correctly by sending yourself a notification:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec kpcyrd/notify-telegram -o bot_token=1337:foobar -o chat_id=1337 'hello world'
|
||||
|
||||
You should receive ``hello world`` from your bot on Telegram.
|
||||
|
||||
Pushover
|
||||
~~~~~~~~
|
||||
|
||||
Install the pushover notification module from the registry:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int pkg install kpcyrd/notify-pushover
|
||||
|
||||
Signup for pushover and configure the app on your device. Copy th user key
|
||||
visible on the pushover dashboard. Click "Create an Application/API Token". Set
|
||||
"sn0int" as name and set an icon if you want to. Copy the api token.
|
||||
|
||||
Test your tokens are working correctly by sending yourself a notification:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec kpcyrd/notify-pushover -o user_key=asdf1337 -o api_token=asdf1337 'hello world'
|
||||
|
||||
You should receive ``hello world`` as a push notification.
|
||||
|
||||
Discord
|
||||
~~~~~~~
|
||||
|
||||
Install the discord notification module from the registry:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int pkg install kpcyrd/notify-discord
|
||||
|
||||
Decide which channel should receive notifications (or create a new one). Open
|
||||
the "Server Settings" of your discord server. Click on "Webhooks". Click
|
||||
"Create Webhook". Configure the Name and Channel. Copy the Webhook URL.
|
||||
|
||||
|
||||
Test your tokens are working correctly by sending yourself a notification:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec kpcyrd/notify-discord -o url=https://discord.com/api/webhooks/1337/asdf 'hello world'
|
||||
|
||||
You should receive ``hello world`` in your discord channel.
|
||||
|
||||
Signal
|
||||
~~~~~~
|
||||
|
||||
Install the sn0int notification module from the registry:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int pkg install kpcyrd/notify-signal
|
||||
|
||||
This module allows end-to-end encrypted notifications, but it's also difficult
|
||||
to setup. You need a second phone number and install both `signal-cli
|
||||
<https://github.com/AsamK/signal-cli>`_ and `sn0int-signal
|
||||
<https://github.com/kpcyrd/sn0int-signal>`_.
|
||||
|
||||
After you've registered your second phone number with signal-cli, you can use
|
||||
sn0int-signal to expose a minimal api for notify-signal. For more detailed
|
||||
instructions and how to start the api at boot, see the `sn0int-signal README
|
||||
<https://github.com/kpcyrd/sn0int-signal>`_.
|
||||
|
||||
Read the secret key generated at ``/etc/sn0int-signal.key`` and send a
|
||||
notification to the signal phone number:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec kpcyrd/notify-signal -o to=+31337 -o secret=asdf 'hello world'
|
||||
|
||||
You should receive ``hello world`` from the number signed up with signal-cli.
|
||||
|
||||
Writing your own module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Make sure you've read the detailed instructions on how to get setup with
|
||||
`module development <scripting.html>`_.
|
||||
|
||||
Create a new sn0int module like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int new ~/repos/sn0int-modules/notify-custom.lua
|
||||
|
||||
Edit the ``-- Source:`` so it takes notifications as input:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: notifications
|
||||
|
||||
function run(arg)
|
||||
-- TODO your code here
|
||||
-- https://sn0int.readthedocs.io/en/stable/reference.html
|
||||
|
||||
debug(arg)
|
||||
info(arg['subject'])
|
||||
info(arg['body'])
|
||||
end
|
||||
|
||||
Execute your script:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec notify-custom 'hello world'
|
||||
|
||||
You most likely need to pass options to avoid hard-coding keys into your
|
||||
script. Options can be fetched like this:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: notifications
|
||||
|
||||
function run(arg)
|
||||
-- TODO your code here
|
||||
-- https://sn0int.readthedocs.io/en/stable/reference.html
|
||||
|
||||
local foo = getopt('foo')
|
||||
if not foo then return 'Missing -o foo= option' end
|
||||
|
||||
info('foo: ' .. foo)
|
||||
info('subject: ' .. arg['subject'])
|
||||
end
|
||||
|
||||
And passed like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int notify exec notify-custom -o "foo=hello world" 'ohai'
|
||||
|
||||
Setting up notification rules
|
||||
-----------------------------
|
||||
|
||||
We now know how to trigger notifications manually, but we would rather trigger
|
||||
notifications if a module runs into something interesting.
|
||||
|
||||
You can setup subscriptions on specific topics and then have a notification
|
||||
script execute automatically.
|
||||
|
||||
Lookup the location of your sn0int config file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int paths
|
||||
|
||||
And open it in an editor of your choice:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vim /home/user/.config/sn0int.toml
|
||||
|
||||
A basic configuration could look like this:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
# You can have multiple notification sections, this one is named
|
||||
# `demo-telegram-integration`
|
||||
# The label can be set to whatever you want, but you may need to add
|
||||
# double-quotes to use some characters.
|
||||
[notifications.demo-telegram-integration]
|
||||
# If this option is present, the notification must originate from one of
|
||||
# the following workspaces.
|
||||
workspaces = ["default", "some-workspace"]
|
||||
# If this option is present, the notification must match one of the
|
||||
# filters. You can use `*` as a wildcard to match everything except `:`.
|
||||
topics = ["activity:harness/activity-ping:*"]
|
||||
# Mandatory: the module to execute.
|
||||
script = "kpcyrd/notify-telegram"
|
||||
# The options to pass to the module, if any.
|
||||
# Can be accessed with `getopt`
|
||||
options = [
|
||||
"bot_token=1337:foobar",
|
||||
"chat_id=1337",
|
||||
]
|
||||
|
||||
All options except ``script`` are optional, but setting filters is highly
|
||||
recommended.
|
||||
|
||||
Testing notifications
|
||||
---------------------
|
||||
|
||||
To test if your configuration works correctly you can create an event manually:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sn0int -w some-workspace notify send activity:harness/activity-ping:dummy "hello world"
|
||||
|
||||
If it matches any of your rules you should receive a push notifications.
|
||||
|
||||
.. note::
|
||||
If you want to test just the routing without actually sending something, add ``--dry-run``.
|
||||
|
||||
Running sn0int automatically
|
||||
----------------------------
|
||||
|
||||
Support for this is going to improve in the future, but you can already set
|
||||
this up if you're ok with a slightly buggy experience.
|
||||
|
||||
Monitors
|
||||
~~~~~~~~
|
||||
|
||||
Some modules are long-running and either wait for an event from a server or
|
||||
have custom polling built in that's usually configurable with an ``-o
|
||||
interval=`` option. If your module has a non-trivial setup phase, an author may
|
||||
take this approach.
|
||||
|
||||
.. code-block::
|
||||
|
||||
# /etc/systemd/system/sn0int-your-new-service.service
|
||||
|
||||
[Unit]
|
||||
Description=sn0int: run example/changeme
|
||||
|
||||
[Service]
|
||||
User=your-user
|
||||
ExecStart=/usr/bin/sn0int run -w your-workspace example/changeme
|
||||
|
||||
Restart=always
|
||||
RestartSec=0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Enable the service to run on boot:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl enable --now sn0int-your-new-service.service
|
||||
|
||||
Timers
|
||||
~~~~~~
|
||||
|
||||
If the module is only one-shot you can set it up to run with a timer:
|
||||
|
||||
.. code-block::
|
||||
|
||||
# /etc/systemd/system/sn0int-your-other-service.service
|
||||
|
||||
[Unit]
|
||||
Description=sn0int: run example/changeme
|
||||
|
||||
[Service]
|
||||
User=your-user
|
||||
ExecStart=/usr/bin/sn0int run -w your-workspace example/changeme
|
||||
|
||||
Setup the timer like this:
|
||||
|
||||
.. code-block::
|
||||
|
||||
# /etc/systemd/system/sn0int-your-other-service.timer
|
||||
|
||||
[Unit]
|
||||
Description=sn0int: run example/changeme
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=1h
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl enable --now sn0int-your-other-service.timer
|
||||
@@ -153,6 +153,28 @@ ttl.
|
||||
device_id=13,
|
||||
}, 120)
|
||||
|
||||
db_activity
|
||||
-----------
|
||||
|
||||
Log an activity event. A basic event looks like this:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
db_activity({
|
||||
topic='harness/activity-ping:dummy',
|
||||
time=sn0int_time(),
|
||||
content={
|
||||
a='b',
|
||||
foo={
|
||||
bar=1337,
|
||||
},
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
|
||||
This function is explained in detail in the `activity <activity.html>`_
|
||||
section.
|
||||
|
||||
db_select
|
||||
---------
|
||||
|
||||
@@ -361,6 +383,10 @@ options are set. The following options are available:
|
||||
The request body that should be json encoded.
|
||||
``form``
|
||||
The request body that should be form encoded.
|
||||
``follow_redirects``
|
||||
Automatically follow redirects, up to the specified number. If set to 1, only
|
||||
one redirect is going to be followed. Defaults to 0 so redirects aren't
|
||||
followed.
|
||||
``body``
|
||||
The raw request body as string.
|
||||
``into_blob``
|
||||
@@ -369,6 +395,8 @@ options are set. The following options are available:
|
||||
``proxy``
|
||||
Use a socks5 proxy in the format ``127.0.0.1:9050``. This option only works
|
||||
if it doesn't conflict with the global proxy settings.
|
||||
``binary``
|
||||
Set to ``true`` to get the http response as raw bytes.
|
||||
|
||||
This function may fail.
|
||||
|
||||
@@ -396,6 +424,8 @@ the following keys:
|
||||
A table of headers
|
||||
``text``
|
||||
The response body as string
|
||||
``binary``
|
||||
The response body as bytes (if ``binary=true``)
|
||||
``blob``
|
||||
If ``into_blob`` was enabled for the request the body is downloaded into blob
|
||||
storage with a reference to the body in this field.
|
||||
@@ -474,6 +504,39 @@ Extract exif metadata from an image.
|
||||
if last_err() then return end
|
||||
debug(exif)
|
||||
|
||||
img_ahash
|
||||
---------
|
||||
|
||||
Calculate the Mean (aHash) perceptual hash.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
hash = img_ahash(blob)
|
||||
if last_err() then return end
|
||||
debug(hash)
|
||||
|
||||
img_dhash
|
||||
---------
|
||||
|
||||
Calculate the Gradient (dHash) perceptual hash.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
hash = img_dhash(blob)
|
||||
if last_err() then return end
|
||||
debug(hash)
|
||||
|
||||
img_phash
|
||||
---------
|
||||
|
||||
Calculate the DCT (pHash) perceptual hash.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
hash = img_phash(blob)
|
||||
if last_err() then return end
|
||||
debug(hash)
|
||||
|
||||
img_nudity
|
||||
----------
|
||||
|
||||
@@ -495,6 +558,13 @@ Log an info to the terminal.
|
||||
|
||||
info('ohai')
|
||||
|
||||
intval
|
||||
------
|
||||
|
||||
Parse a number from a string.
|
||||
|
||||
x = strval('1234')
|
||||
|
||||
json_decode
|
||||
-----------
|
||||
|
||||
@@ -529,6 +599,17 @@ Encode a datastructure into a string.
|
||||
})
|
||||
print(x)
|
||||
|
||||
key_trunc_pad
|
||||
-------------
|
||||
|
||||
Truncate/pad a key to a given length.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- if longer than 32 bytes: truncate to 32
|
||||
-- if shorter than 32 bytes: pad with \x00
|
||||
local key = key_trunc_pad(password, 32, 0)
|
||||
|
||||
keyring
|
||||
-------
|
||||
|
||||
@@ -563,6 +644,64 @@ Hash a byte array with md5 and return the results as bytes.
|
||||
|
||||
hex(md5("\x00\xff"))
|
||||
|
||||
mqtt_connect
|
||||
------------
|
||||
|
||||
Connect to an mqtt broker.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local sock = mqtt_connect('mqtts://mqtt.example.com', {
|
||||
username='foo',
|
||||
password='secret',
|
||||
})
|
||||
if last_err() then return end
|
||||
|
||||
mqtt_subscribe
|
||||
--------------
|
||||
|
||||
Subscribe to a topic. Right now only QoS 0 is supported.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
mqtt_subscribe(sock, '#', 0)
|
||||
if last_err() then return end
|
||||
|
||||
mqtt_recv
|
||||
---------
|
||||
|
||||
Receive an mqtt packet. This is not necessarily a publish packet and more
|
||||
packets might be added in the future, so you need to check the type
|
||||
specifically.
|
||||
|
||||
If a read timeout has been set with mqtt_connect_ this function returns ``nil``
|
||||
in case of a read timeout.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local pkt = mqtt_recv(sock)
|
||||
if last_err() then return end
|
||||
if pkt == nil then
|
||||
-- read timeout, consider sending a ping or disconnect if the previous ping failed
|
||||
elseif pkt['type'] == 'pong' then
|
||||
-- broker sent a pong
|
||||
elseif pkt['type'] == 'publish' then
|
||||
local payload = utf8_decode(pkt['body'])
|
||||
if last_err() then return end
|
||||
info(payload)
|
||||
end
|
||||
|
||||
mqtt_ping
|
||||
---------
|
||||
|
||||
Send a pingreq packet, causing the broker to send a pingresp. This is used to
|
||||
make sure the connection is still working correctly.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
mqtt_ping(sock)
|
||||
if last_err() then return end
|
||||
|
||||
pgp_pubkey
|
||||
----------
|
||||
|
||||
@@ -643,6 +782,22 @@ Returns the parent domain according to the public suffix list. For
|
||||
domain = psl_domain_from_dns_name('www.a.b.c.d.example.co.uk')
|
||||
print(domain == 'example.co.uk')
|
||||
|
||||
ratelimit_throttle
|
||||
------------------
|
||||
|
||||
Create a ratelimit that can only be passed x times every y milliseconds. This
|
||||
limit is global for a single ``run`` and also works with threads.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
-- allow this to pass every 250ms
|
||||
ratelimit_throttle('foo', 1, 250)
|
||||
-- allow this to pass not more than 4 times per second
|
||||
ratelimit_throttle('foo', 4, 1000)
|
||||
|
||||
This is useful if you need to coordinate your executions to stay below a
|
||||
certain request threshold.
|
||||
|
||||
regex_find
|
||||
----------
|
||||
|
||||
@@ -818,6 +973,13 @@ The following options are available:
|
||||
``proxy``
|
||||
Use a socks5 proxy in the format ``127.0.0.1:9050``. This option only works
|
||||
if it doesn't conflict with the global proxy settings.
|
||||
``connect_timeout``
|
||||
Abort tcp connection attempts after ``n`` seconds.
|
||||
``read_timeout``
|
||||
Abort read attempts after ``n`` seconds. This can be used to wake up
|
||||
connections periodically.
|
||||
``write_timeout``
|
||||
Abort write attempts after ``n`` seconds.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
@@ -849,6 +1011,23 @@ discarded when using sock_connect_ directly with ``tls=true``.
|
||||
|
||||
info(tls)
|
||||
|
||||
sock_options
|
||||
------------
|
||||
|
||||
Update options of an existing connection:
|
||||
|
||||
``read_timeout``
|
||||
Abort read attempts after ``n`` seconds. This can be used to wake up
|
||||
connections periodically.
|
||||
``write_timeout``
|
||||
Abort write attempts after ``n`` seconds.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
sock_options(sock, {
|
||||
read_timeout=3,
|
||||
})
|
||||
|
||||
sock_send
|
||||
---------
|
||||
|
||||
@@ -950,6 +1129,27 @@ Overwrite the default ``\n`` newline.
|
||||
|
||||
sock_newline(sock, "\r\n")
|
||||
|
||||
sodium_secretbox_open
|
||||
---------------------
|
||||
|
||||
Use authenticated symetric crypto to decrypt a given message.
|
||||
|
||||
Internally this is ``crypto_secretbox_xsalsa20poly1305``.
|
||||
|
||||
The key **must** be 32 bytes, see key_trunc_pad_ if necessary.
|
||||
|
||||
The first 24 bytes of the encrypted message are expected to be the nonce.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
plain = sodium_secretbox_open(encrypted, key)
|
||||
if last_err() then return end
|
||||
|
||||
txt = utf8_decode(plain)
|
||||
if last_err() then return end
|
||||
|
||||
info(txt)
|
||||
|
||||
status
|
||||
------
|
||||
|
||||
@@ -985,6 +1185,32 @@ Read stdin until EOF as a utf-8 string.
|
||||
.. note::
|
||||
This only works with `sn0int run --stdin`.
|
||||
|
||||
str_find
|
||||
--------
|
||||
|
||||
Returns the byte index of the first character that matches the pattern. This is
|
||||
explicitly a literal match instead of a lua pattern.
|
||||
|
||||
If no match is found, returns ``nil``.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
x = str_find('asdf', 'sd')
|
||||
print(x == 2)
|
||||
|
||||
str_replace
|
||||
-----------
|
||||
|
||||
Replaces all matches of a pattern in a string. This is explicitly a literal
|
||||
match instead of a lua pattern.
|
||||
|
||||
If no match is found, an unmodified copy is returned.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
x = str_replace('this is old', 'old', 'new')
|
||||
print(x == 'this is new')
|
||||
|
||||
strftime
|
||||
--------
|
||||
|
||||
@@ -1005,6 +1231,13 @@ Parse a date into a unix timestamp, see `strftime rules`_.
|
||||
|
||||
.. _strftime rules: https://docs.rs/chrono/0.4.6/chrono/format/strftime/index.html
|
||||
|
||||
strval
|
||||
------
|
||||
|
||||
Convert a number into a string.
|
||||
|
||||
x = strval(1234)
|
||||
|
||||
time_unix
|
||||
---------
|
||||
|
||||
@@ -1125,6 +1358,111 @@ a ``run`` execution.
|
||||
warn_once('ohai')
|
||||
warn_once('ohai')
|
||||
|
||||
ws_connect
|
||||
----------
|
||||
|
||||
Create a websocket connection. The url format is ``ws://example.com/asdf``,
|
||||
``wss://`` is also supported.
|
||||
|
||||
The following options are available:
|
||||
|
||||
``headers``
|
||||
A map of additional headers that should be set for the request.
|
||||
``proxy``
|
||||
Use a socks5 proxy in the format ``127.0.0.1:9050``. This option only works
|
||||
if it doesn't conflict with the global proxy settings.
|
||||
``connect_timeout``
|
||||
Abort tcp connection attempts after ``n`` seconds.
|
||||
``read_timeout``
|
||||
Abort read attempts after ``n`` seconds. This can be used to wake up
|
||||
connections periodically.
|
||||
``write_timeout``
|
||||
Abort write attempts after ``n`` seconds.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
sock = ws_connect("wss://example.com/asdf", {})
|
||||
|
||||
ws_options
|
||||
----------
|
||||
|
||||
Update options of an existing connection:
|
||||
|
||||
``read_timeout``
|
||||
Abort read attempts after ``n`` seconds. This can be used to wake up
|
||||
connections periodically.
|
||||
``write_timeout``
|
||||
Abort write attempts after ``n`` seconds.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
ws_options(sock, {
|
||||
read_timeout=3,
|
||||
})
|
||||
|
||||
ws_recv_text
|
||||
------------
|
||||
|
||||
Wait until the server sends a text frame. A binary frame is considered an
|
||||
error. Ping requests are answered automatically.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
msg = ws_recv_text(sock)
|
||||
|
||||
ws_recv_binary
|
||||
--------------
|
||||
|
||||
Wait until the server sends a binary frame. A text frame is considered an
|
||||
error. Ping requests are answered automatically.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
msg = ws_recv_binary(sock)
|
||||
|
||||
ws_recv_json
|
||||
------------
|
||||
|
||||
Identical to ws_send_text_ but automatically runs json_decode_ on the
|
||||
response.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
msg = ws_recv_json(sock)
|
||||
|
||||
ws_send_text
|
||||
------------
|
||||
|
||||
Send a text frame on the websocket connection.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
ws_send_text(sock, "ohai!")
|
||||
|
||||
ws_send_binary
|
||||
--------------
|
||||
|
||||
Send a binary frame on the websocket connection.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
ws_send_binary(sock, "\x00\x01\x02")
|
||||
|
||||
ws_send_json
|
||||
------------
|
||||
|
||||
Encode the object as json string and send it as a text frame on the websocket
|
||||
connection.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
ws_send_text(sock, {
|
||||
foo="ohai!",
|
||||
x={
|
||||
y={1,3,3,7},
|
||||
},
|
||||
})
|
||||
|
||||
x509_parse_pem
|
||||
--------------
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ child process. It's important to note that there's a basic sandbox that's
|
||||
active on every operating system, and there's a second line of defense on
|
||||
supported operating systems.
|
||||
|
||||
The first line of defense is the restrictive stdlib. It's assumed that and
|
||||
The first line of defense is the restrictive stdlib. It's assumed that an
|
||||
attacker gains full control over the lua code and is able to call any function
|
||||
with arbitrary arguments. The stdlib only provides functions that are
|
||||
considered safe, so for example it's not possible to start a process or open a
|
||||
@@ -119,7 +119,7 @@ You might experience a sandbox failure, especially on architectures that are
|
||||
less popular. This usually looks like this::
|
||||
|
||||
[sn0int][example][kpcyrd/ctlogs] > run
|
||||
[-] Failed "example.com": EOF while parsing a value at line 1 column 0
|
||||
[-] Failed "example.com": Sandbox child has crashed
|
||||
[+] Finished kpcyrd/ctlogs (1 errors)
|
||||
|
||||
A module that never finishes could also mean an IO thread inside the worker got
|
||||
|
||||
@@ -259,6 +259,35 @@ is ``140.82.112.0/20``.
|
||||
This field isn't strictly defined and meant to be used as a human
|
||||
meaningful name if available.
|
||||
|
||||
CryptoAddrs
|
||||
-----------
|
||||
|
||||
A cryptoaddr is any cryptocurrency address and not tied to a specific currency.
|
||||
|
||||
``value``
|
||||
The address string. This looks like ``1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2``.
|
||||
``currency``
|
||||
The identifier for a specific currency. This is usually the ticker symbols,
|
||||
like ``xbt``, ``zec`` or ``xmr``.
|
||||
``denominator``
|
||||
Balance is tracked internally using 64 bit integers (signed, for technical reasons). Balance is supposed to be the lowest unit, so in case of bitcoin you'd write ``100,000,000`` satoshi instead of ``1`` bitcoin. Since this value is inconvinient to work with we're using the denominator to display values. In case of bitcoin you'd set it to ``8``.
|
||||
``balance``
|
||||
The current balance of the address, in the lowest possible unit. In case of bitcoin this would be satoshis.
|
||||
``received``
|
||||
The total amount of currency received by this address.
|
||||
``first_seen``
|
||||
The first time currency was sent to this address.
|
||||
``last_withdrawal``
|
||||
The last time a transaction signed by this address was observed.
|
||||
``description``
|
||||
A human readable note for this address.
|
||||
|
||||
Activity
|
||||
--------
|
||||
|
||||
Activity is different from all other structs, have a look at the `Activity
|
||||
Section <activity.html>`_.
|
||||
|
||||
Relations
|
||||
---------
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@ number of recommended modules::
|
||||
[+] Downloading "GeoLite2-City.mmdb"
|
||||
[+] Downloading "GeoLite2-ASN.mmdb"
|
||||
[+] Loaded 0 modules
|
||||
[*] No modules found, run quickstart to install default modules
|
||||
[*] No modules found, run pkg quickstart to install default modules
|
||||
[sn0int][default] >
|
||||
|
||||
Typing ``quickstart`` is going to get you a fair number of featured modules::
|
||||
Typing ``pkg quickstart`` is going to get you a fair number of featured modules::
|
||||
|
||||
[sn0int][default] > quickstart
|
||||
[sn0int][default] > pkg quickstart
|
||||
[+] Installing kpcyrd/asn
|
||||
[+] Installing kpcyrd/ctlogs
|
||||
[+] Installing kpcyrd/dns-resolve
|
||||
@@ -105,7 +105,7 @@ Running a module
|
||||
Now that we have something to get started with, we can run our first module.
|
||||
First lets list all modules we have::
|
||||
|
||||
[sn0int][demo] > mod list
|
||||
[sn0int][demo] > pkg list
|
||||
kpcyrd/asn (0.1.0)
|
||||
Run a asn lookup for an ip address
|
||||
kpcyrd/ctlogs (0.1.0)
|
||||
@@ -149,7 +149,7 @@ some of them in a browser but hold on, there's a more efficient way to approach
|
||||
this.
|
||||
|
||||
.. hint::
|
||||
You can run the modules concurrently with ``run -j 8``.
|
||||
You can run the modules concurrently with ``run -j3``.
|
||||
|
||||
Running followup modules on the results
|
||||
---------------------------------------
|
||||
@@ -256,85 +256,3 @@ You can reverse this using the scope command::
|
||||
.. hint::
|
||||
All entities have this field, you can refer to it in queries using
|
||||
``unscoped=1``.
|
||||
|
||||
Autonoscope
|
||||
-----------
|
||||
|
||||
Instead of manually unscoping everything you can also define so called
|
||||
autonoscope rules. Those are executed from most specific to least specific and
|
||||
the first match wins. If no rule matches, the default is in-scope::
|
||||
|
||||
[sn0int][demo] > # add the domain first
|
||||
[sn0int][demo] > # this is necessary because we only want to partially unscope example.com
|
||||
[sn0int][demo] > add domain example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > # automatically noscope all subdomains
|
||||
[sn0int][demo] > autonoscope add domain example.com
|
||||
[sn0int][demo] > # except subdomains of prod.example.com
|
||||
[sn0int][demo] > autoscope add domain prod.example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > autonoscope list
|
||||
scope domain "prod.example.com"
|
||||
noscope domain "example.com"
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > # this is going to be out-of-scope
|
||||
[sn0int][demo] > add subdomain www.example.com
|
||||
[sn0int][demo] > # this is going to be in-scope
|
||||
[sn0int][demo] > add subdomain db.prod.example.com
|
||||
[sn0int][demo] >
|
||||
[sn0int][demo] > select subdomains
|
||||
#1, "www.example.com"
|
||||
#2, "db.prod.example.com"
|
||||
[sn0int][demo] > select subdomains where unscoped=0
|
||||
#2, "db.prod.example.com"
|
||||
[sn0int][demo] > select subdomains where unscoped=1
|
||||
#1, "www.example.com"
|
||||
[sn0int][demo] >
|
||||
|
||||
Domains
|
||||
~~~~~~~
|
||||
|
||||
Autonoscope rules for domains are applied to the following structs:
|
||||
|
||||
- domains
|
||||
- subdomains
|
||||
- urls
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add domain example.com
|
||||
autonoscope add domain staging.example.com
|
||||
autonoscope add domain com
|
||||
autonoscope add domain .
|
||||
|
||||
IPs
|
||||
~~~
|
||||
|
||||
Autonoscope rules for IPs are applied to the following structs:
|
||||
|
||||
- ipaddrs
|
||||
- netblocks
|
||||
- ports
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add ip 0.0.0.0/0
|
||||
autonoscope add ip ::/0
|
||||
autonoscope add ip 192.168.0.0/16
|
||||
autonoscope add ip 10.13.33.37/32
|
||||
|
||||
URLs
|
||||
~~~~
|
||||
|
||||
Autonoscope rules for urls are applied to the following structs:
|
||||
|
||||
- urls
|
||||
|
||||
Note that these rules are specific to a certain origin (like
|
||||
``https://example.com``) and are used to filter paths.
|
||||
|
||||
Example rules::
|
||||
|
||||
autonoscope add url https://example.com/
|
||||
autonoscope add url https://example.com/admin/
|
||||
autonoscope add url https://example.com/a/b/c/d
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[macro_use] extern crate boxxy;
|
||||
extern crate sn0int;
|
||||
#[macro_use]
|
||||
extern crate boxxy;
|
||||
extern crate env_logger;
|
||||
extern crate sn0int;
|
||||
|
||||
fn stage1(sh: &mut boxxy::Shell, _args: Vec<String>) -> Result<(), boxxy::Error> {
|
||||
shprintln!(sh, "[*] starting stage1");
|
||||
@@ -14,8 +15,6 @@ fn main() {
|
||||
|
||||
println!("stage1 activate sandbox");
|
||||
|
||||
let toolbox = boxxy::Toolbox::new().with(vec![
|
||||
("stage1", stage1),
|
||||
]);
|
||||
let toolbox = boxxy::Toolbox::new().with(vec![("stage1", stage1)]);
|
||||
boxxy::Shell::new(toolbox).run()
|
||||
}
|
||||
|
||||
@@ -1,61 +1,27 @@
|
||||
extern crate sn0int;
|
||||
extern crate env_logger;
|
||||
extern crate chrootable_https;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
// workaround for rustc 1.29.2 support
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
extern crate structopt;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
#[macro_use] extern crate structopt;
|
||||
|
||||
use clap::Parser;
|
||||
use sn0int::errors::*;
|
||||
use sn0int::geoip::{AsnDB, GeoIP, Maxmind};
|
||||
use sn0int::paths;
|
||||
use std::fs;
|
||||
use std::net::IpAddr;
|
||||
use structopt::StructOpt;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Args {
|
||||
#[structopt(name="dl")]
|
||||
Download(Download),
|
||||
#[structopt(name="asn")]
|
||||
#[command(name = "asn")]
|
||||
Asn(AsnArgs),
|
||||
#[structopt(name="geoip")]
|
||||
#[command(name = "geoip")]
|
||||
GeoIP(GeoIPArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct Download {
|
||||
url: String,
|
||||
filter: String,
|
||||
target: String,
|
||||
#[structopt(short="e", long="extract-only")]
|
||||
extract_only: bool,
|
||||
}
|
||||
|
||||
impl Download {
|
||||
fn run(&self) -> Result<()> {
|
||||
let path = paths::cache_dir()?.join(&self.target);
|
||||
if self.extract_only {
|
||||
let body = fs::read(&self.url)?;
|
||||
sn0int::archive::extract(&mut &body[..], &self.filter, path)?;
|
||||
} else {
|
||||
GeoIP::download(path, &self.filter, &self.url)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct AsnArgs {
|
||||
ip: IpAddr,
|
||||
}
|
||||
|
||||
impl AsnArgs {
|
||||
fn run(&self) -> Result<()> {
|
||||
let asndb = AsnDB::open_or_download()?;
|
||||
fn run(&self, cache_dir: &Path) -> Result<()> {
|
||||
let path = AsnDB::cache_path(cache_dir)?;
|
||||
let asndb = AsnDB::open(&path)?;
|
||||
|
||||
let asn = asndb.lookup(self.ip)?;
|
||||
println!("{:#?}", asn);
|
||||
@@ -64,14 +30,15 @@ impl AsnArgs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct GeoIPArgs {
|
||||
ip: IpAddr,
|
||||
}
|
||||
|
||||
impl GeoIPArgs {
|
||||
fn run(&self) -> Result<()> {
|
||||
let geoip = GeoIP::open_or_download()?;
|
||||
fn run(&self, cache_dir: &Path) -> Result<()> {
|
||||
let path = GeoIP::cache_path(cache_dir)?;
|
||||
let geoip = GeoIP::open(&path)?;
|
||||
|
||||
let lookup = geoip.lookup(self.ip)?;
|
||||
println!("{:#?}", lookup);
|
||||
@@ -80,14 +47,13 @@ impl GeoIPArgs {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let args = Args::from_args();
|
||||
let args = Args::parse();
|
||||
debug!("{:?}", args);
|
||||
let cache_dir = paths::cache_dir()?;
|
||||
match args {
|
||||
Args::Download(args) => args.run(),
|
||||
Args::Asn(args) => args.run(),
|
||||
Args::GeoIP(args) => args.run(),
|
||||
Args::Asn(args) => args.run(&cache_dir),
|
||||
Args::GeoIP(args) => args.run(&cache_dir),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
use clap::Parser;
|
||||
use sn0int::term::{Spinner, StackedSpinners, SPINNERS};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use sn0int::term::{SPINNERS, Spinner, StackedSpinners};
|
||||
use structopt::StructOpt;
|
||||
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Args {
|
||||
#[structopt(name="single")]
|
||||
#[command(name = "single")]
|
||||
Single(Single),
|
||||
#[structopt(name="stacked")]
|
||||
#[command(name = "stacked")]
|
||||
Stacked(Stacked),
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Single {
|
||||
idx: usize,
|
||||
#[structopt(long="ticks", default_value="100")]
|
||||
#[structopt(long = "ticks", default_value = "100")]
|
||||
ticks: usize,
|
||||
}
|
||||
|
||||
@@ -32,9 +31,8 @@ impl Single {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct Stacked {
|
||||
}
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Stacked {}
|
||||
|
||||
impl Stacked {
|
||||
fn run(&self) {
|
||||
@@ -59,7 +57,7 @@ impl Stacked {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::from_args();
|
||||
let args = Args::parse();
|
||||
match args {
|
||||
Args::Single(args) => args.run(),
|
||||
Args::Stacked(args) => args.run(),
|
||||
|
||||
31
migrations/2020-01-09-024234_activity/down.sql
Normal file
31
migrations/2020-01-09-024234_activity/down.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
DROP TABLE activity;
|
||||
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
-- ports
|
||||
CREATE TABLE _ports_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
ip_addr_id INTEGER NOT NULL,
|
||||
value VARCHAR NOT NULL,
|
||||
ip_addr VARCHAR NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
protocol VARCHAR NOT NULL,
|
||||
status VARCHAR NOT NULL,
|
||||
unscoped BOOLEAN DEFAULT 0 NOT NULL,
|
||||
|
||||
banner VARCHAR,
|
||||
service VARCHAR,
|
||||
version VARCHAR,
|
||||
|
||||
FOREIGN KEY(ip_addr_id) REFERENCES ipaddrs(id) ON DELETE CASCADE,
|
||||
CONSTRAINT port_unique UNIQUE (value)
|
||||
);
|
||||
|
||||
INSERT INTO _ports_new (id, ip_addr_id, value, ip_addr, port, protocol, status, unscoped, banner, service, version)
|
||||
SELECT id, ip_addr_id, value, ip_addr, port, protocol, status, unscoped, banner, service, version
|
||||
FROM ports;
|
||||
|
||||
DROP TABLE ports;
|
||||
ALTER TABLE _ports_new RENAME TO ports;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
44
migrations/2020-01-09-024234_activity/up.sql
Normal file
44
migrations/2020-01-09-024234_activity/up.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
topic VARCHAR NOT NULL,
|
||||
time DATETIME NOT NULL,
|
||||
uniq VARCHAR,
|
||||
latitude FLOAT,
|
||||
longitude FLOAT,
|
||||
radius INTEGER,
|
||||
content VARCHAR NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX activity_uniq ON activity(topic, uniq);
|
||||
CREATE INDEX activity_topic ON activity(topic);
|
||||
CREATE INDEX activity_time ON activity(time);
|
||||
CREATE INDEX activity_topic_time ON activity(topic, time);
|
||||
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
-- ports
|
||||
CREATE TABLE _ports_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
ip_addr_id INTEGER NOT NULL,
|
||||
value VARCHAR NOT NULL,
|
||||
ip_addr VARCHAR NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
protocol VARCHAR NOT NULL,
|
||||
status VARCHAR,
|
||||
unscoped BOOLEAN DEFAULT 0 NOT NULL,
|
||||
|
||||
banner VARCHAR,
|
||||
service VARCHAR,
|
||||
version VARCHAR,
|
||||
|
||||
FOREIGN KEY(ip_addr_id) REFERENCES ipaddrs(id) ON DELETE CASCADE,
|
||||
CONSTRAINT port_unique UNIQUE (value)
|
||||
);
|
||||
|
||||
INSERT INTO _ports_new (id, ip_addr_id, value, ip_addr, port, protocol, status, unscoped, banner, service, version)
|
||||
SELECT id, ip_addr_id, value, ip_addr, port, protocol, status, unscoped, banner, service, version
|
||||
FROM ports;
|
||||
|
||||
DROP TABLE ports;
|
||||
ALTER TABLE _ports_new RENAME TO ports;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
18
migrations/2020-06-12-222250_ttl-values/down.sql
Normal file
18
migrations/2020-06-12-222250_ttl-values/down.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
CREATE TABLE _ttls_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
family VARCHAR NOT NULL,
|
||||
key INTEGER NOT NULL,
|
||||
expire DATETIME NOT NULL,
|
||||
CONSTRAINT ttl_unique UNIQUE (family, key)
|
||||
);
|
||||
|
||||
INSERT INTO _ttls_new (id, family, key, expire)
|
||||
SELECT id, family, key, expire
|
||||
FROM ttls;
|
||||
|
||||
DROP TABLE ttls;
|
||||
ALTER TABLE _ttls_new RENAME TO ttls;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
19
migrations/2020-06-12-222250_ttl-values/up.sql
Normal file
19
migrations/2020-06-12-222250_ttl-values/up.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
CREATE TABLE _ttls_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
family VARCHAR NOT NULL,
|
||||
key INTEGER NOT NULL,
|
||||
value VARCHAR NOT NULL,
|
||||
expire DATETIME NOT NULL,
|
||||
CONSTRAINT ttl_unique UNIQUE (family, key)
|
||||
);
|
||||
|
||||
INSERT INTO _ttls_new (id, family, key, value, expire)
|
||||
SELECT id, family, key, "unknown", expire
|
||||
FROM ttls;
|
||||
|
||||
DROP TABLE ttls;
|
||||
ALTER TABLE _ttls_new RENAME TO ttls;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
26
modules/harness/activity-ping-once.lua
Normal file
26
modules/harness/activity-ping-once.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Description: Log some dummy activity
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
local uniq = getopt('uniq')
|
||||
local topic = getopt('topic') or 'harness/activity-ping:dummy'
|
||||
|
||||
if getopt('gps') then
|
||||
lat=1.23
|
||||
lon=4.56
|
||||
radius=100
|
||||
end
|
||||
|
||||
db_activity({
|
||||
topic=topic,
|
||||
time=sn0int_time(),
|
||||
uniq=uniq,
|
||||
latitude=lat,
|
||||
longitude=lon,
|
||||
radius=radius,
|
||||
content={
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
end
|
||||
29
modules/harness/activity-ping.lua
Normal file
29
modules/harness/activity-ping.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
-- Description: Log some dummy activity
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
local uniq = getopt('uniq')
|
||||
local topic = getopt('topic') or 'harness/activity-ping:dummy'
|
||||
|
||||
if getopt('gps') then
|
||||
lat=1.23
|
||||
lon=4.56
|
||||
radius=100
|
||||
end
|
||||
|
||||
while true do
|
||||
db_activity({
|
||||
topic=topic,
|
||||
time=sn0int_time(),
|
||||
uniq=uniq,
|
||||
latitude=lat,
|
||||
longitude=lon,
|
||||
radius=radius,
|
||||
content={
|
||||
msg='ohai',
|
||||
},
|
||||
})
|
||||
sleep(5)
|
||||
end
|
||||
end
|
||||
26
modules/harness/commit-log.lua
Normal file
26
modules/harness/commit-log.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
-- git log -s --format='%H %ci'
|
||||
|
||||
function run()
|
||||
while true do
|
||||
local x = stdin_readline()
|
||||
if x == nil then
|
||||
break
|
||||
end
|
||||
local m = regex_find('^(\\S+) (.+)', x)
|
||||
if m then
|
||||
time = strptime('%Y-%m-%d %T %z', m[3])
|
||||
time = sn0int_time_from(time)
|
||||
|
||||
db_activity({
|
||||
topic='harness/sn0int-commit:dummy',
|
||||
time=time,
|
||||
uniq=m[2],
|
||||
content={},
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
10
modules/harness/dummy-resolve.lua
Normal file
10
modules/harness/dummy-resolve.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: subdomains
|
||||
|
||||
function run(arg)
|
||||
db_update('subdomain', arg, {
|
||||
resolvable=true,
|
||||
})
|
||||
end
|
||||
13
modules/harness/dummy-subdomains.lua
Normal file
13
modules/harness/dummy-subdomains.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: domains
|
||||
|
||||
function run(arg)
|
||||
for i=1, 20 do
|
||||
db_add('subdomain', {
|
||||
domain_id=arg['id'],
|
||||
value=http_mksession() .. '.' .. arg['value'],
|
||||
})
|
||||
end
|
||||
end
|
||||
74
modules/harness/geo-polygon-contains.lua
Normal file
74
modules/harness/geo-polygon-contains.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
-- Description: demonstrate geofencing with polygons
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
hamburg = {
|
||||
{ lat=53.63975308945899, lon=9.764785766601562 },
|
||||
{ lat=53.59494998253459, lon=9.827270507812 },
|
||||
{ lat=53.663153974456456, lon=9.9151611328125 },
|
||||
{ lat=53.65582987649682, lon=9.976272583007812 },
|
||||
{ lat=53.68613523817129, lon=9.992752075195312 },
|
||||
{ lat=53.68674518938816, lon=10.051460266113281 },
|
||||
{ lat=53.72495117617815, lon=10.075492858886719 },
|
||||
{ lat=53.71946627930625, lon=10.118408203125 },
|
||||
{ lat=53.743635083157756, lon=10.164413452148438 },
|
||||
{ lat=53.73104466704585, lon=10.202865600585938 },
|
||||
{ lat=53.676781546441546, lon=10.16304016113281 },
|
||||
{ lat=53.632832079199474, lon=10.235824584960938 },
|
||||
{ lat=53.608803292930894, lon=10.2008056640625 },
|
||||
{ lat=53.578646152866504, lon=10.208358764648438 },
|
||||
{ lat=53.57212285981298, lon=10.163726806640625 },
|
||||
{ lat=53.52071674896369, lon=10.18707275390625 },
|
||||
{ lat=53.52643162253097, lon=10.224151611328125 },
|
||||
{ lat=53.44062753992289, lon=10.347747802734375 },
|
||||
{ lat=53.38824275010831, lon=10.248870849609375 },
|
||||
{ lat=53.38824275010831, lon=10.15960693359375 },
|
||||
{ lat=53.44635321212876, lon=10.064849853515625 },
|
||||
{ lat=53.40595029739904, lon=9.985198974609375 },
|
||||
{ lat=53.42385506057106, lon=9.951210021972656 },
|
||||
{ lat=53.41843327091211, lon=9.944171905517578 },
|
||||
{ lat=53.41812635648326, lon=9.927349090576172 },
|
||||
{ lat=53.412294561442884, lon=9.917736053466797 },
|
||||
{ lat=53.41464783813818, lon=9.901256561279297 },
|
||||
{ lat=53.443490472483326, lon=9.912586212158201 },
|
||||
{ lat=53.45177144115704, lon=9.897651672363281 },
|
||||
{ lat=53.43633277935392, lon=9.866924285888672 },
|
||||
{ lat=53.427639673754776, lon=9.866409301757812 },
|
||||
{ lat=53.427639673754776, lon=9.858856201171875 },
|
||||
{ lat=53.46710230573499, lon=9.795513153076172 },
|
||||
{ lat=53.49039461941655, lon=9.795341491699219 },
|
||||
{ lat=53.49029248806277, lon=9.77903366088867 },
|
||||
{ lat=53.49856433088649, lon=9.780235290527344 },
|
||||
{ lat=53.5078554643033, lon=9.758434295654297 },
|
||||
{ lat=53.545407634092975, lon=9.759807586669922 },
|
||||
{ lat=53.568147234570084, lon=9.633293151855469 },
|
||||
{ lat=53.58802162343514, lon=9.655780792236328 },
|
||||
{ lat=53.568351121879815, lon=9.727706909179688 },
|
||||
{ lat=53.60921067445695, lon=9.737663269042969 },
|
||||
}
|
||||
|
||||
points = {
|
||||
{
|
||||
name='Alice',
|
||||
lat=52.52437,
|
||||
lon=13.41053,
|
||||
}, {
|
||||
name='Bob',
|
||||
lat=53.551085,
|
||||
lon=9.993682,
|
||||
}, {
|
||||
name='Charlie',
|
||||
lat=40.726662,
|
||||
lon=-74.036677,
|
||||
}
|
||||
}
|
||||
|
||||
for i=1, #points do
|
||||
if geo_polygon_contains(hamburg, points[i]) then
|
||||
info('[INSIDE ] ' .. points[i]['name'])
|
||||
else
|
||||
info('[OUTSIDE] ' .. points[i]['name'])
|
||||
end
|
||||
end
|
||||
end
|
||||
29
modules/harness/google-tls.lua
Normal file
29
modules/harness/google-tls.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
-- Description: Test various tls functions
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
info('sending https request to google.com')
|
||||
session = http_mksession()
|
||||
req = http_request(session, 'GET', 'https://google.com/', {})
|
||||
r = http_send(req)
|
||||
if last_err() then return end
|
||||
debug(r)
|
||||
|
||||
info('creating tls socket to google.com')
|
||||
sock = sock_connect('google.com', 443, {
|
||||
tls=true,
|
||||
})
|
||||
if last_err() then return end
|
||||
debug(sock)
|
||||
|
||||
info('creating socket to google.com, wrapping afterwards')
|
||||
sock = sock_connect('google.com', 443, {})
|
||||
if last_err() then return end
|
||||
tls = sock_upgrade_tls(sock, {
|
||||
sni_value='google.com',
|
||||
})
|
||||
if last_err() then return end
|
||||
debug(sock)
|
||||
debug(tls)
|
||||
end
|
||||
11
modules/harness/img-hash.lua
Normal file
11
modules/harness/img-hash.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: images
|
||||
|
||||
function run(arg)
|
||||
debug(arg)
|
||||
info(img_ahash(arg['value']))
|
||||
info(img_dhash(arg['value']))
|
||||
info(img_phash(arg['value']))
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
-- echo 'www.example.com' | sn0int run -vvf --stdin modules/harness/import-subdomains.lu
|
||||
-- echo 'www.example.com' | sn0int run -vvf --stdin modules/harness/import-subdomains.lua
|
||||
|
||||
while true do
|
||||
local line = stdin_readline()
|
||||
|
||||
8
modules/harness/notify-ohai.lua
Normal file
8
modules/harness/notify-ohai.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: notifications
|
||||
|
||||
function run(arg)
|
||||
info('notication!: ' .. json_encode(arg))
|
||||
end
|
||||
9
modules/harness/notify-ratelimit.lua
Normal file
9
modules/harness/notify-ratelimit.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: notifications
|
||||
|
||||
function run(arg)
|
||||
ratelimit_throttle('foo', 3, 10000)
|
||||
info('notication!: ' .. json_encode(arg))
|
||||
end
|
||||
10
modules/harness/notify-slow.lua
Normal file
10
modules/harness/notify-slow.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Source: notifications
|
||||
|
||||
function run(arg)
|
||||
-- TODO your code here
|
||||
sleep(1)
|
||||
info('notication!: ' .. json_encode(arg))
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
-- Description: Send a request to a hidden service
|
||||
-- Version: 0.1.0
|
||||
-- Stealth: passive
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
|
||||
22
modules/harness/pgp-fetch.lua
Normal file
22
modules/harness/pgp-fetch.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
session = http_mksession()
|
||||
url = 'https://openpgpkey.archlinux.org/.well-known/openpgpkey/archlinux.org/hu/in9mwr4s84x7gm51851h343n3at1x61g?l=anthraxx'
|
||||
|
||||
req = http_request(session, 'GET', url, {})
|
||||
r = http_fetch(req)
|
||||
-- debug(r)
|
||||
k = pgp_pubkey(r['text'])
|
||||
info(k)
|
||||
|
||||
req = http_request(session, 'GET', url, {
|
||||
binary=true,
|
||||
})
|
||||
r = http_fetch(req)
|
||||
-- debug(r)
|
||||
k = pgp_pubkey(r['binary'])
|
||||
info(k)
|
||||
end
|
||||
11
modules/harness/ratelimit.lua
Normal file
11
modules/harness/ratelimit.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Description: Run script with a global ratelimit
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
-- this shouldn't complete in less than 5 seconds
|
||||
for i=1, 20 do
|
||||
ratelimit_throttle('foo', 4, 1000)
|
||||
info(sn0int_time())
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
-- Description: basic selftest
|
||||
-- Version: 0.1.0
|
||||
-- Stealth: offline
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
|
||||
33
modules/harness/socket-ping.lua
Normal file
33
modules/harness/socket-ping.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Description: Connect somewhere and send a ping every 3s
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
INTERVAL = 3
|
||||
|
||||
function run()
|
||||
local sock = sock_connect('127.0.0.1', 4444, {
|
||||
read_timeout=INTERVAL,
|
||||
})
|
||||
if last_err() then return end
|
||||
|
||||
local last_ping = time_unix()
|
||||
while true do
|
||||
local now = time_unix()
|
||||
local sleep = last_ping + INTERVAL - now
|
||||
|
||||
if sleep <= 0 then
|
||||
sock_send(sock, sn0int_time() .. ' ping\n')
|
||||
last_ping = now
|
||||
sleep = INTERVAL
|
||||
end
|
||||
|
||||
sock_options(sock, {
|
||||
read_timeout=sleep,
|
||||
})
|
||||
if last_err() then return end
|
||||
|
||||
local buf = sock_recv(sock)
|
||||
if last_err() then return end
|
||||
info(buf)
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
-- Description: Read from stdin
|
||||
-- Version: 0.1.0
|
||||
-- Stealth: offline
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
|
||||
58
modules/harness/url-inserter.lua
Normal file
58
modules/harness/url-inserter.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
info('preparing')
|
||||
domain_id = db_add('domain', {
|
||||
value='example.com',
|
||||
})
|
||||
subdomain_id = db_add('subdomain', {
|
||||
domain_id=domain_id,
|
||||
value='example.com',
|
||||
})
|
||||
|
||||
info('inserting')
|
||||
url1 = db_add('url', {
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com',
|
||||
})
|
||||
url2 = db_add('url', {
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com/ohai',
|
||||
body='ohai',
|
||||
})
|
||||
url3 = db_add('url', {
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com/world',
|
||||
body={0x77, 0x6f, 0x72, 0x6c, 0x64},
|
||||
})
|
||||
|
||||
info('updating')
|
||||
db_update('url', {
|
||||
id=url1,
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com',
|
||||
path='/',
|
||||
unscoped=false,
|
||||
}, {
|
||||
})
|
||||
db_update('url', {
|
||||
id=url2,
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com/ohai',
|
||||
path='/ohai',
|
||||
body='ohai',
|
||||
unscoped=false,
|
||||
}, {
|
||||
})
|
||||
db_update('url', {
|
||||
id=url3,
|
||||
subdomain_id=subdomain_id,
|
||||
value='https://example.com/world',
|
||||
path='/world',
|
||||
body={0x77, 0x6f, 0x72, 0x6c, 0x64},
|
||||
unscoped=false,
|
||||
}, {
|
||||
})
|
||||
end
|
||||
26
modules/harness/winkekatze-sub.lua
Normal file
26
modules/harness/winkekatze-sub.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Description: TODO your description here
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
local sock = mqtt_connect('mqtt://mqtt.winkekatze24.de', {
|
||||
read_timeout=10,
|
||||
})
|
||||
if last_err() then return end
|
||||
mqtt_subscribe(sock, '#', 0)
|
||||
|
||||
while true do
|
||||
-- read the next mqtt packet
|
||||
local pkt = mqtt_recv(sock)
|
||||
if last_err() then return end
|
||||
|
||||
local text
|
||||
if pkt then
|
||||
-- attempt to utf8 decode the body if there was a pkt
|
||||
text = utf8_decode(pkt['body'])
|
||||
if last_err() then clear_err() end
|
||||
end
|
||||
|
||||
info({pkt=pkt, text=text})
|
||||
end
|
||||
end
|
||||
33
modules/harness/ws-ping.lua
Normal file
33
modules/harness/ws-ping.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Description: Connect somewhere and send a ping every 3s
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
INTERVAL = 3
|
||||
|
||||
function run()
|
||||
local sock = ws_connect('ws://127.0.0.1:8080', {
|
||||
read_timeout=INTERVAL,
|
||||
})
|
||||
if last_err() then return end
|
||||
|
||||
local last_ping = time_unix()
|
||||
while true do
|
||||
local now = time_unix()
|
||||
local sleep = last_ping + INTERVAL - now
|
||||
|
||||
if sleep <= 0 then
|
||||
ws_send_text(sock, sn0int_time() .. ' ping\n')
|
||||
last_ping = now
|
||||
sleep = INTERVAL
|
||||
end
|
||||
|
||||
ws_options(sock, {
|
||||
read_timeout=sleep,
|
||||
})
|
||||
if last_err() then return end
|
||||
|
||||
local buf = ws_recv_text(sock)
|
||||
if last_err() then return end
|
||||
info(buf)
|
||||
end
|
||||
end
|
||||
23
modules/harness/ws.lua
Normal file
23
modules/harness/ws.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Description: Create an echo websocket connection
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
local target = 'ws://echo.websocket.org'
|
||||
|
||||
info('connecting to ' .. target)
|
||||
local sock = ws_connect(target, {})
|
||||
if last_err() then return end
|
||||
|
||||
info('sending')
|
||||
ws_send_text(sock, 'ohai wurld')
|
||||
if last_err() then return end
|
||||
|
||||
info('recieving')
|
||||
local msg = ws_recv_text(sock)
|
||||
if last_err() then return end
|
||||
|
||||
if msg ~= 'ohai wurld' then
|
||||
return 'echo failed, got: ' .. msg
|
||||
end
|
||||
end
|
||||
30
modules/harness/wss.lua
Normal file
30
modules/harness/wss.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- Description: Create an encrypted websocket connection
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
|
||||
function run()
|
||||
-- local target = 'ws://echo.websocket.org' -- doesn't support proper ciphers
|
||||
local target = 'wss://rocket.events.ccc.de/sockjs/258/whi0yr1y/websocket'
|
||||
|
||||
info('connecting to ' .. target)
|
||||
local sock = ws_connect(target, {})
|
||||
if last_err() then return end
|
||||
|
||||
info('recieving 1/2')
|
||||
local msg = ws_recv_text(sock)
|
||||
if last_err() then return end
|
||||
|
||||
if msg ~= 'o' then
|
||||
return 'recieve failed, got ' .. msg
|
||||
end
|
||||
|
||||
info('recieving 2/2')
|
||||
local msg = ws_recv_text(sock)
|
||||
if last_err() then return end
|
||||
|
||||
if msg ~= 'a["{\\"server_id\\":\\"0\\"}"]' then
|
||||
return 'recieve failed, got ' .. msg
|
||||
end
|
||||
|
||||
info('handshake succeeded')
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
-- Description: jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
|
||||
-- Version: 0.1.0
|
||||
-- License: GPL-3.0
|
||||
-- Stealth: loud
|
||||
-- Source: domains
|
||||
|
||||
function run()
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "sn0int-common"
|
||||
version = "0.9.0"
|
||||
description = "Common code for sn0int"
|
||||
version = "0.14.0"
|
||||
description = "sn0int - common code"
|
||||
authors = ["kpcyrd <git@rxv.cc>"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/kpcyrd/sn0int"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde = { version = "1.0", features=["derive"] }
|
||||
#rocket_failure_errors = { path = "../../../rocket_failure/rocket_failure_errors" }
|
||||
rocket_failure_errors = "0.2"
|
||||
failure = "0.1"
|
||||
nom = "5.0"
|
||||
anyhow = "1.0"
|
||||
nom = "7.0"
|
||||
clap = { version = "4.3.11", features = ["derive"] }
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::id::ModuleID;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WhoamiResponse {
|
||||
2
sn0int-common/src/errors.rs
Normal file
2
sn0int-common/src/errors.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub use anyhow::{anyhow, bail, format_err, Context, Error};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::errors::*;
|
||||
use nom;
|
||||
use serde::{de, Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
#[inline(always)]
|
||||
fn valid_char(c: char) -> bool {
|
||||
nom::character::is_alphanumeric(c as u8) || c == '-'
|
||||
@@ -20,15 +18,15 @@ pub fn valid_name(name: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
fn module(s: &str) -> nom::IResult<&str, ModuleID> {
|
||||
let (input, (author, _, name)) = nom::sequence::tuple((
|
||||
token,
|
||||
nom::bytes::complete::tag("/"),
|
||||
token,
|
||||
))(s)?;
|
||||
Ok((input, ModuleID {
|
||||
author: author.to_string(),
|
||||
name: name.to_string(),
|
||||
}))
|
||||
let (input, (author, _, name)) =
|
||||
nom::sequence::tuple((token, nom::bytes::complete::tag("/"), token))(s)?;
|
||||
Ok((
|
||||
input,
|
||||
ModuleID {
|
||||
author: author.to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -36,7 +34,7 @@ fn token(s: &str) -> nom::IResult<&str, &str> {
|
||||
nom::bytes::complete::take_while1(valid_char)(s)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ModuleID {
|
||||
pub author: String,
|
||||
pub name: String,
|
||||
@@ -52,8 +50,8 @@ impl FromStr for ModuleID {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<ModuleID> {
|
||||
let (trailing, module) = module(s)
|
||||
.map_err(|err| format_err!("Failed to parse module id: {:?}", err))?;
|
||||
let (trailing, module) =
|
||||
module(s).map_err(|err| anyhow!("Failed to parse module id: {:?}", err))?;
|
||||
if !trailing.is_empty() {
|
||||
bail!("Trailing data in module id");
|
||||
}
|
||||
@@ -72,7 +70,8 @@ impl Serialize for ModuleID {
|
||||
|
||||
impl<'de> Deserialize<'de> for ModuleID {
|
||||
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||
@@ -86,10 +85,13 @@ mod tests {
|
||||
#[test]
|
||||
fn verify_valid() {
|
||||
let result = ModuleID::from_str("kpcyrd/foo").expect("parse");
|
||||
assert_eq!(result, ModuleID {
|
||||
author: "kpcyrd".to_string(),
|
||||
name: "foo".to_string(),
|
||||
});
|
||||
assert_eq!(
|
||||
result,
|
||||
ModuleID {
|
||||
author: "kpcyrd".to_string(),
|
||||
name: "foo".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1,12 +1,8 @@
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate nom;
|
||||
|
||||
pub mod api;
|
||||
pub mod errors;
|
||||
pub use crate::errors::*;
|
||||
pub mod metadata;
|
||||
pub mod id;
|
||||
pub mod metadata;
|
||||
pub use crate::id::*;
|
||||
|
||||
pub use rocket_failure_errors::StrictApiResponse as ApiResponse;
|
||||
@@ -1,7 +1,13 @@
|
||||
use crate::errors::*;
|
||||
|
||||
use nom::bytes::complete::{tag, take_until};
|
||||
use nom::combinator::map_res;
|
||||
use nom::multi::fold_many0;
|
||||
use nom::IResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
mod stealth;
|
||||
pub use self::stealth::Stealth;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum EntryType {
|
||||
@@ -9,6 +15,9 @@ pub enum EntryType {
|
||||
Version,
|
||||
Source,
|
||||
KeyringAccess,
|
||||
Stealth,
|
||||
Author,
|
||||
Repository,
|
||||
License,
|
||||
}
|
||||
|
||||
@@ -21,6 +30,9 @@ impl FromStr for EntryType {
|
||||
"Version" => Ok(EntryType::Version),
|
||||
"Source" => Ok(EntryType::Source),
|
||||
"Keyring-Access" => Ok(EntryType::KeyringAccess),
|
||||
"Stealth" => Ok(EntryType::Stealth),
|
||||
"Author" => Ok(EntryType::Author),
|
||||
"Repository" => Ok(EntryType::Repository),
|
||||
"License" => Ok(EntryType::License),
|
||||
x => bail!("Unknown EntryType: {:?}", x),
|
||||
}
|
||||
@@ -44,6 +56,7 @@ pub enum Source {
|
||||
Netblocks,
|
||||
CryptoAddrs(Option<String>),
|
||||
KeyRing(String),
|
||||
Notifications,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
@@ -63,6 +76,7 @@ impl Source {
|
||||
Source::Ports => "ports",
|
||||
Source::Netblocks => "netblocks",
|
||||
Source::CryptoAddrs(_) => "cryptoaddrs",
|
||||
Source::Notifications => "notifications",
|
||||
Source::KeyRing(_) => "keyring",
|
||||
}
|
||||
}
|
||||
@@ -94,6 +108,7 @@ impl FromStr for Source {
|
||||
("ports", None) => Ok(Source::Ports),
|
||||
("netblocks", None) => Ok(Source::Netblocks),
|
||||
("cryptoaddrs", param) => Ok(Source::CryptoAddrs(param.map(String::from))),
|
||||
("notifications", None) => Ok(Source::Notifications),
|
||||
("keyring", Some(param)) => Ok(Source::KeyRing(param.to_string())),
|
||||
(x, Some(param)) => bail!("Unknown Source: {:?} ({:?})", x, param),
|
||||
(x, None) => bail!("Unknown Source: {:?}", x),
|
||||
@@ -133,6 +148,9 @@ pub struct Metadata {
|
||||
pub version: String,
|
||||
pub source: Option<Source>,
|
||||
pub keyring_access: Vec<String>,
|
||||
pub stealth: Stealth,
|
||||
pub authors: Vec<String>,
|
||||
pub repository: Option<String>,
|
||||
pub license: License,
|
||||
}
|
||||
|
||||
@@ -140,8 +158,7 @@ impl FromStr for Metadata {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(code: &str) -> Result<Metadata> {
|
||||
let (_, lines) = metalines(code)
|
||||
.map_err(|_| format_err!("Failed to parse header"))?;
|
||||
let (_, lines) = metalines(code).map_err(|_| format_err!("Failed to parse header"))?;
|
||||
|
||||
let mut data = NewMetadata::default();
|
||||
|
||||
@@ -151,6 +168,9 @@ impl FromStr for Metadata {
|
||||
EntryType::Version => data.version = Some(v),
|
||||
EntryType::Source => data.source = Some(v),
|
||||
EntryType::KeyringAccess => data.keyring_access.push(v),
|
||||
EntryType::Stealth => data.stealth = Some(v),
|
||||
EntryType::Author => data.authors.push(v),
|
||||
EntryType::Repository => data.repository = Some(v),
|
||||
EntryType::License => data.license = Some(v),
|
||||
}
|
||||
}
|
||||
@@ -165,21 +185,34 @@ pub struct NewMetadata<'a> {
|
||||
pub version: Option<&'a str>,
|
||||
pub source: Option<&'a str>,
|
||||
pub keyring_access: Vec<&'a str>,
|
||||
pub stealth: Option<&'a str>,
|
||||
pub authors: Vec<&'a str>,
|
||||
pub repository: Option<&'a str>,
|
||||
pub license: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> NewMetadata<'a> {
|
||||
fn try_from(self) -> Result<Metadata> {
|
||||
let description = self.description.ok_or_else(|| format_err!("Description is required"))?;
|
||||
let version = self.version.ok_or_else(|| format_err!("Version is required"))?;
|
||||
let description = self
|
||||
.description
|
||||
.ok_or_else(|| format_err!("Description is required"))?;
|
||||
let version = self
|
||||
.version
|
||||
.ok_or_else(|| format_err!("Version is required"))?;
|
||||
let source = match self.source {
|
||||
Some(x) => Some(x.parse()?),
|
||||
_ => None,
|
||||
};
|
||||
let keyring_access = self.keyring_access.into_iter()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let license = self.license.ok_or_else(|| format_err!("License is required"))?;
|
||||
let keyring_access = self.keyring_access.into_iter().map(String::from).collect();
|
||||
let stealth = match self.stealth {
|
||||
Some(x) => x.parse()?,
|
||||
_ => Stealth::Normal,
|
||||
};
|
||||
let authors = self.authors.into_iter().map(String::from).collect();
|
||||
let repository = self.repository.map(String::from);
|
||||
let license = self
|
||||
.license
|
||||
.ok_or_else(|| format_err!("License is required"))?;
|
||||
let license = license.parse()?;
|
||||
|
||||
Ok(Metadata {
|
||||
@@ -187,28 +220,33 @@ impl<'a> NewMetadata<'a> {
|
||||
version: version.to_string(),
|
||||
source,
|
||||
keyring_access,
|
||||
stealth,
|
||||
authors,
|
||||
repository,
|
||||
license,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
named!(metaline<&str, (EntryType, &str)>, do_parse!(
|
||||
tag!("-- ") >>
|
||||
name: map_res!(take_until!(": "), EntryType::from_str) >>
|
||||
tag!(": ") >>
|
||||
value: take_until!("\n") >>
|
||||
tag!("\n") >>
|
||||
(name, value)
|
||||
));
|
||||
fn metaline(input: &str) -> IResult<&str, (EntryType, &str)> {
|
||||
let (input, _) = tag("-- ")(input)?;
|
||||
let (input, name) = map_res(take_until(": "), EntryType::from_str)(input)?;
|
||||
let (input, _) = tag(": ")(input)?;
|
||||
let (input, value) = take_until("\n")(input)?;
|
||||
let (input, _) = tag("\n")(input)?;
|
||||
|
||||
named!(metalines<&str, Vec<(EntryType, &str)>>, do_parse!(
|
||||
lines: fold_many0!(metaline, Vec::new(), |mut acc: Vec<_>, item| {
|
||||
Ok((input, (name, value)))
|
||||
}
|
||||
|
||||
fn metalines(input: &str) -> IResult<&str, Vec<(EntryType, &str)>> {
|
||||
let (input, lines) = fold_many0(metaline, Vec::new, |mut acc: Vec<_>, item| {
|
||||
acc.push(item);
|
||||
acc
|
||||
}) >>
|
||||
tag!("\n") >>
|
||||
(lines)
|
||||
));
|
||||
})(input)?;
|
||||
let (input, _) = tag("\n")(input)?;
|
||||
|
||||
Ok((input, lines))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -216,55 +254,110 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_simple() {
|
||||
let metadata = Metadata::from_str(r#"-- Description: Hello world, this is my description
|
||||
let metadata = Metadata::from_str(
|
||||
r#"-- Description: Hello world, this is my description
|
||||
-- Version: 1.0.0
|
||||
-- Source: domains
|
||||
-- License: WTFPL
|
||||
|
||||
"#).expect("parse");
|
||||
assert_eq!(metadata, Metadata {
|
||||
description: "Hello world, this is my description".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
license: License::WTFPL,
|
||||
source: Some(Source::Domains),
|
||||
keyring_access: Vec::new(),
|
||||
});
|
||||
"#,
|
||||
)
|
||||
.expect("parse");
|
||||
assert_eq!(
|
||||
metadata,
|
||||
Metadata {
|
||||
description: "Hello world, this is my description".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
license: License::WTFPL,
|
||||
source: Some(Source::Domains),
|
||||
stealth: Stealth::Normal,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
keyring_access: Vec::new(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_much_metadata() {
|
||||
let metadata = Metadata::from_str(
|
||||
r#"-- Description: Hello world, this is my description
|
||||
-- Version: 1.0.0
|
||||
-- Source: domains
|
||||
-- Stealth: passive
|
||||
-- Author: kpcyrd <git at rxv dot cc>
|
||||
-- Author: kpcyrd's cat
|
||||
-- Repository: https://github.com/kpcyrd/sn0int
|
||||
-- License: WTFPL
|
||||
|
||||
"#,
|
||||
)
|
||||
.expect("parse");
|
||||
assert_eq!(
|
||||
metadata,
|
||||
Metadata {
|
||||
description: "Hello world, this is my description".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
license: License::WTFPL,
|
||||
source: Some(Source::Domains),
|
||||
stealth: Stealth::Passive,
|
||||
authors: vec![
|
||||
"kpcyrd <git at rxv dot cc>".to_string(),
|
||||
"kpcyrd's cat".to_string(),
|
||||
],
|
||||
repository: Some("https://github.com/kpcyrd/sn0int".to_string()),
|
||||
keyring_access: Vec::new(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_no_source() {
|
||||
let metadata = Metadata::from_str(r#"-- Description: Hello world, this is my description
|
||||
let metadata = Metadata::from_str(
|
||||
r#"-- Description: Hello world, this is my description
|
||||
-- Version: 1.0.0
|
||||
-- License: WTFPL
|
||||
|
||||
"#).expect("parse");
|
||||
assert_eq!(metadata, Metadata {
|
||||
description: "Hello world, this is my description".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
license: License::WTFPL,
|
||||
source: None,
|
||||
keyring_access: Vec::new(),
|
||||
});
|
||||
"#,
|
||||
)
|
||||
.expect("parse");
|
||||
assert_eq!(
|
||||
metadata,
|
||||
Metadata {
|
||||
description: "Hello world, this is my description".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
license: License::WTFPL,
|
||||
source: None,
|
||||
stealth: Stealth::Normal,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
keyring_access: Vec::new(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_require_license() {
|
||||
let metadata = Metadata::from_str(r#"-- Description: Hello world, this is my description
|
||||
let metadata = Metadata::from_str(
|
||||
r#"-- Description: Hello world, this is my description
|
||||
-- Version: 1.0.0
|
||||
-- Source: domains
|
||||
|
||||
"#);
|
||||
"#,
|
||||
);
|
||||
assert!(metadata.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_require_opensource_license() {
|
||||
let metadata = Metadata::from_str(r#"-- Description: Hello world, this is my description
|
||||
let metadata = Metadata::from_str(
|
||||
r#"-- Description: Hello world, this is my description
|
||||
-- Version: 1.0.0
|
||||
-- Source: domains
|
||||
-- License: Proprietary
|
||||
|
||||
"#);
|
||||
"#,
|
||||
);
|
||||
assert!(metadata.is_err());
|
||||
}
|
||||
|
||||
49
sn0int-common/src/metadata/stealth.rs
Normal file
49
sn0int-common/src/metadata/stealth.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::errors::*;
|
||||
use clap::ValueEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Clone, ValueEnum, Serialize, Deserialize)]
|
||||
pub enum Stealth {
|
||||
Loud,
|
||||
Normal,
|
||||
Passive,
|
||||
Offline,
|
||||
}
|
||||
|
||||
impl Stealth {
|
||||
#[inline]
|
||||
pub fn variants() -> &'static [&'static str] {
|
||||
&["loud", "normal", "passive", "offline"]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_num(&self) -> u8 {
|
||||
match self {
|
||||
Stealth::Loud => 3,
|
||||
Stealth::Normal => 2,
|
||||
Stealth::Passive => 1,
|
||||
Stealth::Offline => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn equal_or_better(&self, other: &Self) -> bool {
|
||||
self.as_num() <= other.as_num()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Stealth {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Stealth> {
|
||||
match s {
|
||||
"loud" => Ok(Stealth::Loud),
|
||||
// This is also the default level if none is provided
|
||||
"normal" => Ok(Stealth::Normal),
|
||||
"passive" => Ok(Stealth::Passive),
|
||||
"offline" => Ok(Stealth::Offline),
|
||||
x => bail!("Unknown stealth: {:?}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ repository = "https://github.com/kpcyrd/sn0int"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
sn0int-common = { version="0.9.0", path="sn0int-common" }
|
||||
sn0int-common = { version="0.14.0", path="../sn0int-common" }
|
||||
rocket = { version = "0.4", default-features=false }
|
||||
#rocket_failure = { path = "../../rocket_failure" }
|
||||
rocket_failure = { version = "0.2" }
|
||||
@@ -23,16 +23,15 @@ oauth2 = "2.0.0"
|
||||
failure = "0.1"
|
||||
url = "1.0"
|
||||
log = "0.4"
|
||||
semver = "0.9.0"
|
||||
semver = "1"
|
||||
lazy_static = "1"
|
||||
blake2 = "0.8.0"
|
||||
hex = "0.4"
|
||||
maplit = "1.0.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
syntect = "3.3"
|
||||
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
dotenv = "0.15"
|
||||
env_logger = "0.7"
|
||||
env_logger = "0.9"
|
||||
structopt = "0.3.21"
|
||||
|
||||
@@ -2,16 +2,16 @@ FROM rust:buster
|
||||
RUN apt-get update -q && apt-get install -yq llvm libclang-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN rustup install nightly
|
||||
WORKDIR /usr/src/sn0int-registry
|
||||
WORKDIR /usr/src/sn0int
|
||||
COPY . .
|
||||
RUN cargo +nightly build --release --verbose
|
||||
RUN cd sn0int-registry && cargo +nightly build --release --verbose
|
||||
RUN strip target/release/sn0int-registry
|
||||
|
||||
FROM debian:buster
|
||||
RUN apt-get update -q && apt-get install -yq libcurl4 libpq5 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=0 /usr/src/sn0int-registry/target/release/sn0int-registry /usr/local/bin/sn0int-registry
|
||||
COPY templates /templates
|
||||
COPY --from=0 /usr/src/sn0int/target/release/sn0int-registry /usr/local/bin/sn0int-registry
|
||||
COPY sn0int-registry/templates /templates
|
||||
ENV ROCKET_ENV=prod \
|
||||
ROCKET_ADDRESS=0.0.0.0 \
|
||||
ROCKET_PORT=8000
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pub use failure::{Error, ResultExt};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -1,5 +1,5 @@
|
||||
use blake2::{Blake2b, Digest};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub static FAVICON: &[u8] = include_bytes!("../assets/favicon.ico");
|
||||
pub static STYLESHEET: &[u8] = include_bytes!("../assets/style.css");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::github;
|
||||
use diesel::pg::PgConnection;
|
||||
use oauth2::basic::BasicClient;
|
||||
use oauth2::prelude::*;
|
||||
use oauth2::{AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, TokenUrl, TokenResponse};
|
||||
use crate::github::GithubAuthenticator;
|
||||
use sn0int_registry::errors::*;
|
||||
use sn0int_registry::models::AuthToken;
|
||||
use url::Url;
|
||||
@@ -67,8 +67,7 @@ impl Authenticator {
|
||||
let access_token = response.access_token();
|
||||
let access_token = access_token.secret().to_string();
|
||||
|
||||
let client = GithubAuthenticator::from_env()?;
|
||||
let user = client.get_username(&access_token)?;
|
||||
let user = github::get_username(&access_token)?;
|
||||
|
||||
AuthToken::create(&AuthToken {
|
||||
id: state,
|
||||
|
||||
@@ -4,7 +4,7 @@ use sn0int_registry::db::Connection;
|
||||
use rocket::http::Status;
|
||||
use rocket::{Request, Outcome};
|
||||
use rocket::request::{self, FromRequest};
|
||||
use crate::github::GithubAuthenticator;
|
||||
use crate::github;
|
||||
|
||||
|
||||
pub struct AuthHeader(String);
|
||||
@@ -12,8 +12,7 @@ pub struct AuthHeader(String);
|
||||
impl AuthHeader {
|
||||
pub fn verify(&self, connection: &Connection) -> Result<String> {
|
||||
let session = AuthToken::read(&self.0, &connection)?;
|
||||
let client = GithubAuthenticator::from_env()?;
|
||||
client.get_username(&session.access_token)
|
||||
github::get_username(&session.access_token)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use failure::{Error, ResultExt};
|
||||
pub use failure::{Error, ResultExt, bail, format_err};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
pub use log::{debug, info};
|
||||
|
||||
pub use rocket_failure::errors::*;
|
||||
|
||||
@@ -1,54 +1,24 @@
|
||||
use serde::Deserialize;
|
||||
use sn0int_registry::errors::*;
|
||||
use std::env;
|
||||
use reqwest;
|
||||
|
||||
pub fn get_username(oauth_token: &str) -> Result<String> {
|
||||
let client = reqwest::Client::new();
|
||||
let mut resp = client.get("https://api.github.com/user")
|
||||
.header("Authorization", format!("token {}", oauth_token))
|
||||
.header("User-Agent", "sn0int-registry")
|
||||
.send()
|
||||
.context("Failed to check access_token")?
|
||||
.error_for_status()
|
||||
.context("Github returned http error")?;
|
||||
|
||||
pub struct GithubAuthenticator {
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
}
|
||||
let data = resp.json::<GithubUser>()
|
||||
.context("Failed to deserialize github reply")?;
|
||||
|
||||
impl GithubAuthenticator {
|
||||
pub fn new(client_id: String, client_secret: String) -> GithubAuthenticator {
|
||||
GithubAuthenticator {
|
||||
client_id,
|
||||
client_secret,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_env() -> Result<GithubAuthenticator> {
|
||||
let client_id = env::var("GITHUB_CLIENT_ID")
|
||||
.context("GITHUB_CLIENT_ID is not set")?;
|
||||
let client_secret = env::var("GITHUB_CLIENT_SECRET")
|
||||
.context("GITHUB_CLIENT_SECRET is not set")?;
|
||||
Ok(GithubAuthenticator::new(client_id, client_secret))
|
||||
}
|
||||
|
||||
pub fn get_username(&self, oauth_token: &str) -> Result<String> {
|
||||
let url = format!("https://api.github.com/applications/{}/tokens/{}", self.client_id, oauth_token);
|
||||
let client = reqwest::Client::new();
|
||||
let mut resp = client.get(&url)
|
||||
.basic_auth(&self.client_id, Some(&self.client_secret))
|
||||
.send()?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
bail!("Github returned: {}", resp.status())
|
||||
}
|
||||
|
||||
let data = resp.json::<GithubReply>()
|
||||
.context("Failed to deserialize github reply")?;
|
||||
|
||||
Ok(data.user.login)
|
||||
}
|
||||
Ok(data.login)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GithubReply {
|
||||
user: GithubUser,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GithubUser {
|
||||
struct GithubUser {
|
||||
login: String,
|
||||
#[serde(rename="type")]
|
||||
user_type: String,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#[macro_use] extern crate diesel;
|
||||
#[macro_use] extern crate diesel_migrations;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
pub mod db;
|
||||
pub mod errors;
|
||||
|
||||
@@ -5,21 +5,16 @@
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate rocket_contrib;
|
||||
#[macro_use] extern crate rocket_failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate maplit;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate failure;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use rocket::fairing::AdHoc;
|
||||
use rocket::http::Header;
|
||||
use rocket_contrib::json::{Json, JsonValue};
|
||||
use rocket_contrib::templates::Template;
|
||||
use dotenv::dotenv;
|
||||
|
||||
use std::env;
|
||||
use sn0int_registry::errors::*;
|
||||
use sn0int_registry::db;
|
||||
use sn0int_registry::errors::*;
|
||||
use std::env;
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub mod assets;
|
||||
pub mod auth;
|
||||
@@ -49,7 +44,13 @@ fn internal_error() -> Json<JsonValue> {
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct Args {
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let _args = Args::from_args();
|
||||
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = env::var("DATABASE_URL")
|
||||
|
||||
@@ -4,6 +4,7 @@ use diesel::pg::PgConnection;
|
||||
use diesel::sql_types::BigInt;
|
||||
use diesel_full_text_search::{plainto_tsquery, TsQueryExtensions};
|
||||
use crate::schema::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::SystemTime;
|
||||
use sn0int_common::ModuleID;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use sn0int_registry::errors::*;
|
||||
use crate::assets::ASSET_REV;
|
||||
use crate::auth::Authenticator;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value;
|
||||
use sn0int_registry::db;
|
||||
use sn0int_registry::errors::*;
|
||||
use rocket::request::Form;
|
||||
use rocket::response::Redirect;
|
||||
use rocket_contrib::templates::Template;
|
||||
use crate::assets::ASSET_REV;
|
||||
use serde_json::{self, Value};
|
||||
|
||||
use maplit::hashmap;
|
||||
|
||||
#[get("/?<auth..>")]
|
||||
pub fn get(auth: Form<OAuth>) -> Template {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use serde::Serialize;
|
||||
use sn0int_registry::errors::*;
|
||||
use sn0int_registry::db;
|
||||
use sn0int_registry::models::*;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use sn0int_registry::errors::*;
|
||||
use crate::assets::*;
|
||||
use lazy_static::lazy_static;
|
||||
use sn0int_registry::db;
|
||||
use sn0int_registry::errors::*;
|
||||
use sn0int_registry::models::*;
|
||||
use rocket_contrib::templates::Template;
|
||||
use std::cmp::Ordering;
|
||||
use syntect::html::ClassedHTMLGenerator;
|
||||
use syntect::parsing::{SyntaxSet, SyntaxReference};
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines();
|
||||
pub static ref SYNTAX: &'static SyntaxReference = SYNTAX_SET.find_syntax_by_extension("lua").unwrap();
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub fn index(connection: db::Connection) -> ApiResult<Template> {
|
||||
@@ -30,14 +37,6 @@ pub fn index(connection: db::Connection) -> ApiResult<Template> {
|
||||
})))
|
||||
}
|
||||
|
||||
use syntect::html::ClassedHTMLGenerator;
|
||||
use syntect::parsing::{SyntaxSet, SyntaxReference};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines();
|
||||
pub static ref SYNTAX: &'static SyntaxReference = SYNTAX_SET.find_syntax_by_extension("lua").unwrap();
|
||||
}
|
||||
|
||||
#[get("/r/<author>/<name>")]
|
||||
pub fn details(author: String, name: String,connection: db::Connection) -> ApiResult<Template> {
|
||||
let module = Module::find(&author, &name, &connection)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
To install a module run:
|
||||
</p>
|
||||
<p class="code"><code>
|
||||
sn0int install kpcyrd/ctlogs
|
||||
sn0int pkg install kpcyrd/ctlogs
|
||||
</code></p>
|
||||
|
||||
{{#each modules}}
|
||||
|
||||
57
sn0int-std/Cargo.toml
Normal file
57
sn0int-std/Cargo.toml
Normal file
@@ -0,0 +1,57 @@
|
||||
[package]
|
||||
name = "sn0int-std"
|
||||
version = "0.26.0"
|
||||
description = "sn0int - stdlib"
|
||||
authors = ["kpcyrd <git@rxv.cc>"]
|
||||
repository = "https://github.com/kpcyrd/sn0int"
|
||||
license = "GPL-3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
failure = "0.1"
|
||||
hlua-badtouch = "0.4"
|
||||
tokio = "0.1.14"
|
||||
rand = "0.8"
|
||||
regex = "1.0"
|
||||
rustls = { version="0.18", features=["dangerous_configuration"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
webpki = "0.21"
|
||||
webpki-roots = "0.21"
|
||||
ct-logs = "0.7"
|
||||
#chrootable-https = { path = "../../chrootable-https" }
|
||||
chrootable-https = "0.16"
|
||||
http = "0.2"
|
||||
bufstream = "0.1.4"
|
||||
pem = "3"
|
||||
url = "2.0"
|
||||
tungstenite = { version = "0.13", default-features = false }
|
||||
kuchiki = "0.8.0"
|
||||
maxminddb = "0.23"
|
||||
x509-parser = "0.13"
|
||||
der-parser = "8"
|
||||
publicsuffix = { version="2", default-features=false }
|
||||
xml-rs = "0.8"
|
||||
geo = "0.25"
|
||||
bytes = "0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
mqtt-protocol = "0.11"
|
||||
sodiumoxide = { version="0.2.5", features=["use-pkg-config"] }
|
||||
|
||||
image = "0.23"
|
||||
kamadak-exif = "0.5.1"
|
||||
img_hash_median = "4.0.0"
|
||||
|
||||
bs58 = "0.5"
|
||||
digest = "0.10"
|
||||
blake2 = "0.10"
|
||||
data-encoding = "2.3.3"
|
||||
thiserror = "1.0.38"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.10"
|
||||
maplit = "1.0.1"
|
||||
118
sn0int-std/src/blobs.rs
Normal file
118
sn0int-std/src/blobs.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use blake2::Blake2bVar;
|
||||
use bytes::Bytes;
|
||||
use data_encoding::BASE64;
|
||||
use digest::{Update, VariableOutput};
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Blob {
|
||||
pub id: String,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
impl Blob {
|
||||
pub fn create(bytes: Bytes) -> Blob {
|
||||
let id = Self::hash(&bytes);
|
||||
Blob { id, bytes }
|
||||
}
|
||||
|
||||
pub fn hash(bytes: &[u8]) -> String {
|
||||
let mut h = Blake2bVar::new(32).unwrap();
|
||||
h.update(bytes);
|
||||
let output = h.finalize_boxed();
|
||||
Self::encode_hash(&output)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn encode_hash(bytes: &[u8]) -> String {
|
||||
let x = bs58::encode(bytes).into_string();
|
||||
format!("{:0<44}", x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Blob {
|
||||
#[inline]
|
||||
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = BASE64.encode(&self.bytes);
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Blob {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
let bytes = BASE64.decode(s.as_bytes()).map_err(de::Error::custom)?;
|
||||
Ok(Blob::create(Bytes::from(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BlobState {
|
||||
fn register_blob(&self, blob: Blob) -> String;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json;
|
||||
|
||||
#[inline]
|
||||
fn blob() -> (Bytes, Blob) {
|
||||
let bytes = Bytes::from(&b"asdf"[..]);
|
||||
(bytes.clone(), Blob::create(bytes))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_create_blob() {
|
||||
let (bytes, blob) = blob();
|
||||
assert_eq!(
|
||||
blob,
|
||||
Blob {
|
||||
id: String::from("DTTV3EjpHBNJx3Zw7eJsVPm4bYXKmNkJQpVNkcvTtTSz"),
|
||||
bytes,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let (_, blob) = blob();
|
||||
let json = serde_json::to_string(&blob).unwrap();
|
||||
assert_eq!(&json, "\"YXNkZg==\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
let (_, blob1) = blob();
|
||||
let blob2: Blob = serde_json::from_str("\"YXNkZg==\"").unwrap();
|
||||
assert_eq!(blob1, blob2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_encoding() {
|
||||
let x = bs58::decode("22es54J4FbFtpb5D1MtBazVuum4TcqCQ7M9JkmYdmJ8W")
|
||||
.into_vec()
|
||||
.unwrap();
|
||||
let x = Blob::encode_hash(&x);
|
||||
assert_eq!(x.len(), 44);
|
||||
assert_eq!(x, "22es54J4FbFtpb5D1MtBazVuum4TcqCQ7M9JkmYdmJ8W");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_encoding_padding() {
|
||||
let x = bs58::decode("r6edvU326yvpXLubYacXXSxf2HzqCgzqHUQvpWyNwei")
|
||||
.into_vec()
|
||||
.unwrap();
|
||||
let x = Blob::encode_hash(&x);
|
||||
assert_eq!(x.len(), 44);
|
||||
assert_eq!(x, "r6edvU326yvpXLubYacXXSxf2HzqCgzqHUQvpWyNwei0");
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,22 @@
|
||||
use crate::errors::*;
|
||||
|
||||
use x509_parser;
|
||||
use der_parser::der::DerObject;
|
||||
use der_parser::ber::{BerObjectContent, BerTag};
|
||||
use der_parser::oid::Oid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AlternativeName {
|
||||
DnsName(String),
|
||||
IpAddr(IpAddr),
|
||||
}
|
||||
|
||||
pub fn san_extension(i: &[u8]) -> Result<Vec<AlternativeName>> {
|
||||
let (rem, seq) = der_parser::der::parse_der_sequence(i)
|
||||
.map_err(|_| format_err!("Failed to parse san extension"))?;
|
||||
|
||||
if !rem.is_empty() {
|
||||
bail!("san extension has trailing garbage");
|
||||
}
|
||||
|
||||
debug!("Decoded sequence: {:?}", seq);
|
||||
if let BerObjectContent::Sequence(seq) = seq.content {
|
||||
seq.into_iter()
|
||||
.map(san_value)
|
||||
.collect()
|
||||
} else {
|
||||
bail!("Expected der sequence");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn san_value(o: DerObject) -> Result<AlternativeName> {
|
||||
debug!("DER object in SAN extension: {:?}", o);
|
||||
|
||||
match (o.class, o.tag, &o.content) {
|
||||
(2, BerTag::Integer, BerObjectContent::Unknown(BerTag::Integer, value)) => san_value_dns(value),
|
||||
(2, BerTag::ObjDescriptor, BerObjectContent::Unknown(BerTag::ObjDescriptor, value)) => san_value_ipaddr(value),
|
||||
_ => bail!("Unexpected object: {:?}", o),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn san_value_dns(v: &[u8]) -> Result<AlternativeName> {
|
||||
debug!("Reading as dns name: {:?}", v);
|
||||
String::from_utf8(v.to_vec())
|
||||
.map(AlternativeName::DnsName)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn san_value_ipaddr(v: &[u8]) -> Result<AlternativeName> {
|
||||
debug!("Reading as ipaddr: {:?}", v);
|
||||
match v.len() {
|
||||
4 => Ok(AlternativeName::IpAddr(Ipv4Addr::from([
|
||||
v[0], v[1], v[2], v[3],
|
||||
]).into())),
|
||||
16 => Ok(AlternativeName::IpAddr(Ipv6Addr::from([
|
||||
v[0], v[1], v[2], v[3],
|
||||
v[4], v[5], v[6], v[7],
|
||||
v[8], v[9], v[10], v[11],
|
||||
v[12], v[13], v[14], v[15],
|
||||
]).into())),
|
||||
_ => Err(format_err!("Invalid ipaddr")),
|
||||
}
|
||||
}
|
||||
use std::net::IpAddr;
|
||||
use x509_parser::certificate::X509Certificate;
|
||||
use x509_parser::extensions::{GeneralName, ParsedExtension};
|
||||
use x509_parser::prelude::*;
|
||||
use x509_parser::x509::X509Version;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Certificate {
|
||||
pub valid_names: Vec<String>,
|
||||
pub valid_ipaddrs: Vec<IpAddr>
|
||||
pub valid_emails: Vec<String>,
|
||||
pub valid_ipaddrs: Vec<IpAddr>,
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
pub fn parse_pem(crt: &str) -> Result<Certificate> {
|
||||
let pem = match x509_parser::pem::pem_to_der(crt.as_bytes()) {
|
||||
let pem = match x509_parser::pem::parse_x509_pem(crt.as_bytes()) {
|
||||
Ok((remaining, pem)) => {
|
||||
if !remaining.is_empty() {
|
||||
bail!("input cert has trailing garbage");
|
||||
@@ -82,64 +25,76 @@ impl Certificate {
|
||||
bail!("input is not a certificate");
|
||||
}
|
||||
pem
|
||||
},
|
||||
}
|
||||
Err(_) => bail!("Failed to parse pem"),
|
||||
};
|
||||
Certificate::from_bytes(&pem.contents)
|
||||
}
|
||||
|
||||
pub fn from_bytes(crt: &[u8]) -> Result<Certificate> {
|
||||
let crt = match x509_parser::parse_x509_der(&crt) {
|
||||
let crt = match X509Certificate::from_der(crt) {
|
||||
Ok((remaining, der)) => {
|
||||
if !remaining.is_empty() {
|
||||
bail!("input cert has trailing garbage");
|
||||
}
|
||||
if der.tbs_certificate.version != 2 {
|
||||
if der.tbs_certificate.version != X509Version::V3 {
|
||||
bail!("unexpected certificate version");
|
||||
}
|
||||
der
|
||||
},
|
||||
}
|
||||
Err(_) => bail!("Failed to parse der"),
|
||||
};
|
||||
|
||||
let mut valid_names = HashSet::new();
|
||||
let mut valid_emails = HashSet::new();
|
||||
let mut valid_ipaddrs = HashSet::new();
|
||||
|
||||
for x in crt.tbs_certificate.subject.rdn_seq {
|
||||
for y in x.set {
|
||||
// CommonName
|
||||
if y.attr_type != Oid::from(&[2, 5, 4, 3]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(x) = y.attr_value.content.as_slice() {
|
||||
let value = String::from_utf8(x.to_vec())?;
|
||||
info!("Found CN in Subject: {:?}", value);
|
||||
valid_names.insert(value);
|
||||
}
|
||||
for attr in crt.subject().iter_common_name() {
|
||||
if let Ok(cn) = attr.as_str() {
|
||||
info!("Found CN in Subject: {:?}", cn);
|
||||
valid_names.insert(cn.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
for x in crt.tbs_certificate.extensions {
|
||||
if x.oid != Oid::from(&[2, 5, 29, 17]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Found san extension: {:?}", x.value);
|
||||
let values = san_extension(x.value)?;
|
||||
|
||||
for v in values {
|
||||
match v {
|
||||
AlternativeName::DnsName(v) => valid_names.insert(v),
|
||||
AlternativeName::IpAddr(v) => valid_ipaddrs.insert(v),
|
||||
};
|
||||
for ext in crt.tbs_certificate.extensions() {
|
||||
if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() {
|
||||
for name in &san.general_names {
|
||||
debug!("Certificate is valid for {:?}", name);
|
||||
match name {
|
||||
GeneralName::DNSName(v) => {
|
||||
valid_names.insert(v.to_string());
|
||||
}
|
||||
GeneralName::RFC822Name(v) => {
|
||||
valid_emails.insert(v.to_string());
|
||||
}
|
||||
GeneralName::IPAddress(v) => {
|
||||
let ip = match v.len() {
|
||||
4 => Some(IpAddr::from([v[0], v[1], v[2], v[3]])),
|
||||
16 => Some(IpAddr::from([
|
||||
v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9],
|
||||
v[10], v[11], v[12], v[13], v[14], v[15],
|
||||
])),
|
||||
_ => {
|
||||
info!("Certificate is valid for invalid ip address: {:?}", v);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(ip) = ip {
|
||||
valid_ipaddrs.insert(ip);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let valid_names = valid_names.into_iter().collect();
|
||||
let valid_emails = valid_emails.into_iter().collect();
|
||||
let valid_ipaddrs = valid_ipaddrs.into_iter().collect();
|
||||
Ok(Certificate {
|
||||
valid_names,
|
||||
valid_emails,
|
||||
valid_ipaddrs,
|
||||
})
|
||||
}
|
||||
@@ -148,11 +103,11 @@ impl Certificate {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use der_parser::parse_der;
|
||||
|
||||
#[test]
|
||||
fn test_parse_pem_github() {
|
||||
let mut x = Certificate::parse_pem(r#"-----BEGIN CERTIFICATE-----
|
||||
let mut x = Certificate::parse_pem(
|
||||
r#"-----BEGIN CERTIFICATE-----
|
||||
MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
|
||||
@@ -193,17 +148,24 @@ Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk
|
||||
myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq
|
||||
WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik=
|
||||
-----END CERTIFICATE-----
|
||||
"#).expect("Failed to parse cert");
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse cert");
|
||||
x.valid_names.sort();
|
||||
assert_eq!(x, Certificate {
|
||||
valid_names: vec!["github.com".into(), "www.github.com".into()],
|
||||
valid_ipaddrs: vec![],
|
||||
});
|
||||
assert_eq!(
|
||||
x,
|
||||
Certificate {
|
||||
valid_names: vec!["github.com".into(), "www.github.com".into()],
|
||||
valid_emails: vec![],
|
||||
valid_ipaddrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_pem_1_1_1_1() {
|
||||
let mut x = Certificate::parse_pem(r#"-----BEGIN CERTIFICATE-----
|
||||
let mut x = Certificate::parse_pem(
|
||||
r#"-----BEGIN CERTIFICATE-----
|
||||
MIID9DCCA3qgAwIBAgIQBWzetBRl/ycHFsBukRYuGTAKBggqhkjOPQQDAjBMMQsw
|
||||
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSYwJAYDVQQDEx1EaWdp
|
||||
Q2VydCBFQ0MgU2VjdXJlIFNlcnZlciBDQTAeFw0xODAzMzAwMDAwMDBaFw0yMDAz
|
||||
@@ -227,39 +189,30 @@ ADBlAjEAjoyy2Ogh1i1/Kh9+psMc1OChlQIvQF6AkojZS8yliar6m8q5nqC3qe0h
|
||||
HR0fExwLAjAueWRnHX4QJ9loqMhsPk3NB0Cs0mStsNDNG6/DpCYw7XmjoG3y1LS7
|
||||
ZkZZmqNn2Q8=
|
||||
-----END CERTIFICATE-----
|
||||
"#).expect("Failed to parse cert");
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse cert");
|
||||
x.valid_names.sort();
|
||||
x.valid_ipaddrs.sort();
|
||||
assert_eq!(x, Certificate {
|
||||
valid_names: vec![
|
||||
"*.cloudflare-dns.com".into(),
|
||||
"cloudflare-dns.com".into(),
|
||||
],
|
||||
valid_ipaddrs: vec![
|
||||
"1.0.0.1".parse().unwrap(),
|
||||
"1.1.1.1".parse().unwrap(),
|
||||
"2606:4700:4700::1001".parse().unwrap(),
|
||||
"2606:4700:4700::1111".parse().unwrap(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_san_extension() {
|
||||
let ext = san_extension(&[48, 28,
|
||||
130, 10, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109,
|
||||
130, 14, 119, 119, 119, 46, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109
|
||||
])
|
||||
.expect("Failed to parse extension");
|
||||
assert_eq!(ext, vec![
|
||||
AlternativeName::DnsName(String::from("github.com")),
|
||||
AlternativeName::DnsName(String::from("www.github.com")),
|
||||
]);
|
||||
assert_eq!(
|
||||
x,
|
||||
Certificate {
|
||||
valid_names: vec!["*.cloudflare-dns.com".into(), "cloudflare-dns.com".into(),],
|
||||
valid_emails: vec![],
|
||||
valid_ipaddrs: vec![
|
||||
"1.0.0.1".parse().unwrap(),
|
||||
"1.1.1.1".parse().unwrap(),
|
||||
"2606:4700:4700::1001".parse().unwrap(),
|
||||
"2606:4700:4700::1111".parse().unwrap(),
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_san_extension() {
|
||||
let mut x = Certificate::parse_pem(r#"-----BEGIN CERTIFICATE-----
|
||||
let mut x = Certificate::parse_pem(
|
||||
r#"-----BEGIN CERTIFICATE-----
|
||||
MIII3jCCB8agAwIBAgIQAp1dOviF3mpYKKObx4fjxjANBgkqhkiG9w0BAQsFADBe
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMR0wGwYDVQQDExRHZW9UcnVzdCBSU0EgQ0EgMjAxODAe
|
||||
@@ -309,91 +262,103 @@ mAlnYDoB0Mj2UIPvIeftkDfF6sURmmZb0/+AMbFDCQYHvZFPI8DFgcagy8og5XJZ
|
||||
gQ+70UdJdM3RWyrd9R66aZwNGkcS6C2wtKCRhztWDMru/wNuyOsYS6JttoTYxRsh
|
||||
z/6Vy8Ga9kigYVsa8ZFMR+Ex
|
||||
-----END CERTIFICATE-----
|
||||
"#).expect("Failed to parse cert");
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse cert");
|
||||
x.valid_names.sort();
|
||||
x.valid_ipaddrs.sort();
|
||||
assert_eq!(x, Certificate {
|
||||
valid_names: vec![
|
||||
"aboutyou.de".into(),
|
||||
"assets.aboutyou.de".into(),
|
||||
"cdn.aboutstatic.com".into(),
|
||||
"cdn.aboutyou-staging.de".into(),
|
||||
"cdn.aboutyou.de".into(),
|
||||
"cdn.edited.de".into(),
|
||||
"cdn.mary-paul.de".into(),
|
||||
"cdn.youandidol.de".into(),
|
||||
"cdn1.aboutyou.de".into(),
|
||||
"cdn2.aboutyou.de".into(),
|
||||
"cdn3.aboutyou.de".into(),
|
||||
"cdn4.aboutyou.de".into(),
|
||||
"cdn5.aboutyou.de".into(),
|
||||
"co-m.aboutyou.de".into(),
|
||||
"co-mapp.aboutyou.de".into(),
|
||||
"co-t.aboutyou.de".into(),
|
||||
"co.aboutyou.de".into(),
|
||||
"edited.de".into(),
|
||||
"files.aboutstatic.com".into(),
|
||||
"images.aboutstatic.com".into(),
|
||||
"img.aboutstatic.com".into(),
|
||||
"img.aboutyou.de".into(),
|
||||
"m-assets.aboutyou.de".into(),
|
||||
"m.aboutyou.de".into(),
|
||||
"media.aboutyou.de".into(),
|
||||
"static.aboutyou.de".into(),
|
||||
"static1.aboutyou.de".into(),
|
||||
"static2.aboutyou.de".into(),
|
||||
"static3.aboutyou.de".into(),
|
||||
"static4.aboutyou.de".into(),
|
||||
"static5.aboutyou.de".into(),
|
||||
"staticmail-cdn.aboutyou.de".into(),
|
||||
"t.aboutyou.de".into(),
|
||||
"witt-weiden.dam.acme.aboutyou.cloud".into(),
|
||||
"witt-weiden.dam.staging.aboutyou.cloud".into(),
|
||||
"www.aboutyou.de".into(),
|
||||
],
|
||||
valid_ipaddrs: vec![],
|
||||
});
|
||||
assert_eq!(
|
||||
x,
|
||||
Certificate {
|
||||
valid_names: vec![
|
||||
"aboutyou.de".into(),
|
||||
"assets.aboutyou.de".into(),
|
||||
"cdn.aboutstatic.com".into(),
|
||||
"cdn.aboutyou-staging.de".into(),
|
||||
"cdn.aboutyou.de".into(),
|
||||
"cdn.edited.de".into(),
|
||||
"cdn.mary-paul.de".into(),
|
||||
"cdn.youandidol.de".into(),
|
||||
"cdn1.aboutyou.de".into(),
|
||||
"cdn2.aboutyou.de".into(),
|
||||
"cdn3.aboutyou.de".into(),
|
||||
"cdn4.aboutyou.de".into(),
|
||||
"cdn5.aboutyou.de".into(),
|
||||
"co-m.aboutyou.de".into(),
|
||||
"co-mapp.aboutyou.de".into(),
|
||||
"co-t.aboutyou.de".into(),
|
||||
"co.aboutyou.de".into(),
|
||||
"edited.de".into(),
|
||||
"files.aboutstatic.com".into(),
|
||||
"images.aboutstatic.com".into(),
|
||||
"img.aboutstatic.com".into(),
|
||||
"img.aboutyou.de".into(),
|
||||
"m-assets.aboutyou.de".into(),
|
||||
"m.aboutyou.de".into(),
|
||||
"media.aboutyou.de".into(),
|
||||
"static.aboutyou.de".into(),
|
||||
"static1.aboutyou.de".into(),
|
||||
"static2.aboutyou.de".into(),
|
||||
"static3.aboutyou.de".into(),
|
||||
"static4.aboutyou.de".into(),
|
||||
"static5.aboutyou.de".into(),
|
||||
"staticmail-cdn.aboutyou.de".into(),
|
||||
"t.aboutyou.de".into(),
|
||||
"witt-weiden.dam.acme.aboutyou.cloud".into(),
|
||||
"witt-weiden.dam.staging.aboutyou.cloud".into(),
|
||||
"www.aboutyou.de".into(),
|
||||
],
|
||||
valid_emails: vec![],
|
||||
valid_ipaddrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_san_value_dns() {
|
||||
let (rem, v) = parse_der(&[130, 10, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109])
|
||||
.expect("Failed to parse san value");
|
||||
assert!(rem.is_empty());
|
||||
println!("{:?}", v);
|
||||
assert_eq!(v, DerObject {
|
||||
class: 2,
|
||||
structured: 0,
|
||||
tag: BerTag::Integer,
|
||||
content: BerObjectContent::Unknown(BerTag::Integer, &[103, 105, 116, 104, 117, 98, 46, 99, 111, 109])
|
||||
});
|
||||
let content = match v.content {
|
||||
BerObjectContent::Unknown(BerTag::Integer, v) => v,
|
||||
_ => panic!("Wrong BerObjectContent"),
|
||||
};
|
||||
let v = san_value_dns(content)
|
||||
.expect("Failed to process san value");
|
||||
assert_eq!(v, AlternativeName::DnsName(String::from("github.com")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_san_value_ipaddr() {
|
||||
let (rem, v) = parse_der(&[135, 4, 1, 1, 1, 1])
|
||||
.expect("Failed to parse san value");
|
||||
assert!(rem.is_empty());
|
||||
println!("{:?}", v);
|
||||
assert_eq!(v, DerObject {
|
||||
class: 2,
|
||||
structured: 0,
|
||||
tag: BerTag::ObjDescriptor,
|
||||
content: BerObjectContent::Unknown(BerTag::ObjDescriptor, &[1, 1, 1, 1])
|
||||
});
|
||||
let content = match v.content {
|
||||
BerObjectContent::Unknown(BerTag::ObjDescriptor, v) => v,
|
||||
_ => panic!("Wrong BerObjectContent"),
|
||||
};
|
||||
let v = san_value_ipaddr(content)
|
||||
.expect("Failed to process san value");
|
||||
assert_eq!(v, AlternativeName::IpAddr("1.1.1.1".parse().unwrap()));
|
||||
fn test_san_email() {
|
||||
let mut x = Certificate::parse_pem(
|
||||
r#"-----BEGIN CERTIFICATE-----
|
||||
MIIE5zCCA8+gAwIBAgIQBvsKfZ5AGSW3Vc8Ldto1hTANBgkqhkiG9w0BAQUFADBp
|
||||
MSQwIgYJKoZIhvcNAQkBFhVwa2lfYWRtaW5Ac3VuZ2FyZC5jb20xJjAkBgNVBAoT
|
||||
HVN1bkdhcmQgQXZhaWxhYmlsaXR5IFNlcnZpY2VzMRkwFwYDVQQDExBTQVMgUHVi
|
||||
bGljIENBIHYxMB4XDTEwMDkwMjE2MzY0OVoXDTExMTAwMTE2MzEwMFowgbQxCzAJ
|
||||
BgNVBAYTAlVTMQswCQYDVQQIEwJPUjERMA8GA1UEBxMIUG9ydGxhbmQxEzARBgoJ
|
||||
kiaJk/IsZAEZFgNjb20xHDAaBgoJkiaJk/IsZAEZFgxqaXZlc29mdHdhcmUxHDAa
|
||||
BgNVBAoTE0ppdmUgU29mdHdhcmUsIEluYy4xEDAOBgNVBAsTB0hvc3RpbmcxIjAg
|
||||
BgNVBAMTGSouaG9zdGVkLmppdmVzb2Z0d2FyZS5jb20wggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC0oornTIyL5YjZMpNwy+V2YJbLqaLrbPrbWFCsNJDx
|
||||
dnubjfR71aW+YYlUZF8zoq4jFetkblCehyvPEb5tD/l3/WZhiXYOziPDrsEVCngF
|
||||
3/b0H3Dyk6mNWBZcNpJkdpOx1YB6Zer8eKzFOr7Qj3aevOR/bEe2NARJIaO0Rjwe
|
||||
YIWY0arKRm6z4nJD8fYAvFV6wRWmHsZO9ci7hiGeW3YL6jQYJqLeuwXm64l0jptb
|
||||
Qg8r8c1V5BXETlvQJL34gUozEl9jDpzR7KoXtErhlU2ytl9Wg+fOxYuWgx8vER0/
|
||||
7Hqc/qD5e7B+NtwgfEio7SvNGA/HhjNxW2Wbrx4qooJRAgMBAAGjggE9MIIBOTAO
|
||||
BgNVHQ8BAf8EBAMCA6gwEQYJYIZIAYb4QgEBBAQDAgbAMCIGA1UdEQQbMBmBF3N1
|
||||
YmplY3RuYW1lQGV4YW1wbGUuY29tMB8GA1UdIwQYMBaAFDhBxvKFgYP96+IaNpI7
|
||||
JmEWgRESMFQGA1UdIARNMEswSQYJKoZIhvcNBQYBMDwwOgYIKwYBBQUHAgEWLmh0
|
||||
dHBzOi8vY2VydGlmaWNhdGUuc3VuZ2FyZC5jb20vU0FTX0NBX0NQUy5wZGYwRQYD
|
||||
VR0fBD4wPDA6oDigNoY0aHR0cHM6Ly9jZXJ0aWZpY2F0ZS5zdW5nYXJkLmNvbS9T
|
||||
QVNfUHVibGljX0NBX3YxLmNybDATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4E
|
||||
FgQUhJ99py6oeCYBzcePPjhOxHVDBvwwDQYJKoZIhvcNAQEFBQADggEBAF/DAJgX
|
||||
f50x8t8Im96AUn4DqC+T0QZIYHihpj2uCwWDbdp5efppqTrk6FrpFOzQy0TRkstb
|
||||
Q3zKSgduedQiwii9qh88O1h2gbSTqfi55ApOIGoCiiRCqio2p4tbKyqPV3Q0eyYw
|
||||
K4f9GAOcawvNsI//mx99ol/ZGEamydeL9G0qiKrhqSxd2TGmFaIVJdu9fh59hos4
|
||||
6t9c4FVyYygdsIeGHkHjpB2bKjZhnJpKRh9dGWctcjdHMITBBqgiRH9OZa/w6SPE
|
||||
UT+11L6q7MSIXSIMV8kJSUUYE92P7bnAqViTIuu/hHnfmIhiy6t7AuT2QHEhqDab
|
||||
EF4l5MwdUqs8FvM=
|
||||
-----END CERTIFICATE-----
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse cert");
|
||||
x.valid_names.sort();
|
||||
x.valid_emails.sort();
|
||||
x.valid_ipaddrs.sort();
|
||||
assert_eq!(
|
||||
x,
|
||||
Certificate {
|
||||
valid_names: vec!["*.hosted.jivesoftware.com".into(),],
|
||||
valid_emails: vec!["subjectname@example.com".into(),],
|
||||
valid_ipaddrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
50
sn0int-std/src/crypto.rs
Normal file
50
sn0int-std/src/crypto.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::errors::*;
|
||||
use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
||||
use std::iter;
|
||||
|
||||
pub fn key_trunc_pad(mut key: &[u8], len: usize, pad: u8) -> Vec<u8> {
|
||||
if key.len() > len {
|
||||
key = &key[..len];
|
||||
}
|
||||
|
||||
let mut key = key.to_vec();
|
||||
key.extend(iter::repeat(pad).take(len - key.len()));
|
||||
key
|
||||
}
|
||||
|
||||
pub fn sodium_secretbox_open(encrypted: &[u8], key: &[u8]) -> Result<Vec<u8>> {
|
||||
if encrypted.len() <= secretbox::NONCEBYTES {
|
||||
bail!("Encrypted message is too short");
|
||||
}
|
||||
|
||||
let key = Key::from_slice(key).ok_or_else(|| format_err!("Key has wrong length"))?;
|
||||
let nonce = Nonce::from_slice(&encrypted[..secretbox::NONCEBYTES])
|
||||
.ok_or_else(|| format_err!("Nonce has wrong length"))?;
|
||||
let ciphertext = &encrypted[secretbox::NONCEBYTES..];
|
||||
let plain = secretbox::open(ciphertext, &nonce, &key)
|
||||
.map_err(|_| format_err!("Failed to decrypt secretbox"))?;
|
||||
Ok(plain)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_key_equal() {
|
||||
let key = key_trunc_pad(&[1, 2, 3, 4, 5], 5, 0);
|
||||
assert_eq!(key, &[1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_trunc() {
|
||||
let key = key_trunc_pad(&[1, 2, 3, 4, 5, 6, 7, 8, 9], 5, 0);
|
||||
assert_eq!(key, &[1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_pad() {
|
||||
let key = key_trunc_pad(&[1, 2, 3], 5, 0);
|
||||
assert_eq!(key, &[1, 2, 3, 0, 0]);
|
||||
}
|
||||
}
|
||||
1
sn0int-std/src/engine/mod.rs
Normal file
1
sn0int-std/src/engine/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod structs;
|
||||
@@ -1,16 +1,13 @@
|
||||
use crate::errors::*;
|
||||
use crate::hlua::{AnyHashableLuaValue, AnyLuaValue, AnyLuaString};
|
||||
use std::collections::{self, HashMap};
|
||||
use crate::hlua::{AnyHashableLuaValue, AnyLuaString, AnyLuaValue};
|
||||
use crate::json::LuaJsonValue;
|
||||
use serde;
|
||||
use serde_json;
|
||||
|
||||
use std::collections::{self, HashMap};
|
||||
|
||||
pub fn from_lua<T>(x: LuaJsonValue) -> Result<T>
|
||||
where for<'de> T: serde::Deserialize<'de>
|
||||
where
|
||||
for<'de> T: serde::Deserialize<'de>,
|
||||
{
|
||||
serde_json::from_value(x.into())
|
||||
.map_err(Error::from)
|
||||
serde_json::from_value(x.into()).map_err(Error::from)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -29,17 +26,33 @@ impl LuaMap {
|
||||
|
||||
#[inline]
|
||||
pub fn insert<K: Into<String>, V: Into<AnyLuaValue>>(&mut self, k: K, v: V) {
|
||||
self.0.insert(AnyHashableLuaValue::LuaString(k.into()), v.into());
|
||||
self.0
|
||||
.insert(AnyHashableLuaValue::LuaString(k.into()), v.into());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_str<K: Into<String>, V: Into<String>>(&mut self, k: K, v: V) {
|
||||
self.0.insert(AnyHashableLuaValue::LuaString(k.into()), AnyLuaValue::LuaString(v.into()));
|
||||
self.0.insert(
|
||||
AnyHashableLuaValue::LuaString(k.into()),
|
||||
AnyLuaValue::LuaString(v.into()),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_num<K: Into<String>>(&mut self, k: K, v: f64) {
|
||||
self.0.insert(AnyHashableLuaValue::LuaString(k.into()), AnyLuaValue::LuaNumber(v));
|
||||
self.0.insert(
|
||||
AnyHashableLuaValue::LuaString(k.into()),
|
||||
AnyLuaValue::LuaNumber(v),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn insert_serde<K: Into<String>, S: serde::Serialize>(&mut self, k: K, v: S) -> Result<()> {
|
||||
let v = serde_json::to_value(v)?;
|
||||
self.0.insert(
|
||||
AnyHashableLuaValue::LuaString(k.into()),
|
||||
LuaJsonValue::from(v).into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,23 +96,23 @@ impl From<Vec<(AnyLuaValue, AnyLuaValue)>> for LuaMap {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<HashMap<AnyHashableLuaValue, AnyLuaValue>> for LuaMap {
|
||||
fn into(self: LuaMap) -> HashMap<AnyHashableLuaValue, AnyLuaValue> {
|
||||
self.0
|
||||
impl From<LuaMap> for HashMap<AnyHashableLuaValue, AnyLuaValue> {
|
||||
fn from(map: LuaMap) -> Self {
|
||||
map.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<AnyLuaValue> for LuaMap {
|
||||
fn into(self: LuaMap) -> AnyLuaValue {
|
||||
impl From<LuaMap> for AnyLuaValue {
|
||||
fn from(map: LuaMap) -> AnyLuaValue {
|
||||
AnyLuaValue::LuaArray(
|
||||
self.into_iter()
|
||||
map.into_iter()
|
||||
.filter_map(|(k, v)| {
|
||||
match k {
|
||||
AnyHashableLuaValue::LuaString(x) => Some((AnyLuaValue::LuaString(x), v)),
|
||||
_ => None, // TODO: unknown types are discarded
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -133,9 +146,9 @@ impl LuaList {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<AnyLuaValue> for LuaList {
|
||||
fn into(self: LuaList) -> AnyLuaValue {
|
||||
AnyLuaValue::LuaArray(self.0)
|
||||
impl From<LuaList> for AnyLuaValue {
|
||||
fn from(list: LuaList) -> AnyLuaValue {
|
||||
AnyLuaValue::LuaArray(list.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,17 +156,20 @@ pub fn byte_array(bytes: AnyLuaValue) -> Result<Vec<u8>> {
|
||||
match bytes {
|
||||
AnyLuaValue::LuaAnyString(bytes) => Ok(bytes.0),
|
||||
AnyLuaValue::LuaString(bytes) => Ok(bytes.into_bytes()),
|
||||
AnyLuaValue::LuaArray(bytes) => {
|
||||
Ok(bytes.into_iter()
|
||||
.map(|num| match num.1 {
|
||||
AnyLuaValue::LuaNumber(num) if num <= 255.0 && num >= 0.0 && (num % 1.0 == 0.0) =>
|
||||
Ok(num as u8),
|
||||
AnyLuaValue::LuaNumber(num) =>
|
||||
Err(format_err!("number is out of range: {:?}", num)),
|
||||
_ => Err(format_err!("unexpected type: {:?}", num)),
|
||||
})
|
||||
.collect::<Result<_>>()?)
|
||||
},
|
||||
AnyLuaValue::LuaArray(bytes) => Ok(bytes
|
||||
.into_iter()
|
||||
.map(|num| match num.1 {
|
||||
AnyLuaValue::LuaNumber(num)
|
||||
if (0.0..=255.0).contains(&num) && (num % 1.0 == 0.0) =>
|
||||
{
|
||||
Ok(num as u8)
|
||||
}
|
||||
AnyLuaValue::LuaNumber(num) => {
|
||||
Err(format_err!("number is out of range: {:?}", num))
|
||||
}
|
||||
_ => Err(format_err!("unexpected type: {:?}", num)),
|
||||
})
|
||||
.collect::<Result<_>>()?),
|
||||
_ => Err(format_err!("invalid type: {:?}", bytes)),
|
||||
}
|
||||
}
|
||||
3
sn0int-std/src/errors.rs
Normal file
3
sn0int-std/src/errors.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub use failure::{bail, format_err, Error, ResultExt};
|
||||
pub use log::{debug, error, info, trace, warn};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
246
sn0int-std/src/geo.rs
Normal file
246
sn0int-std/src/geo.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
use crate::errors::*;
|
||||
use crate::hlua::AnyLuaValue;
|
||||
use crate::json::LuaJsonValue;
|
||||
use geo::prelude::*;
|
||||
use geo::{Coord, LineString, Polygon};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Point {
|
||||
lon: f64,
|
||||
lat: f64,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn try_from(x: AnyLuaValue) -> Result<Point> {
|
||||
let x = LuaJsonValue::from(x);
|
||||
let x = serde_json::from_value(x.into())?;
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn polygon_contains(ring: &[Point], p: &Point) -> bool {
|
||||
let ring = ring
|
||||
.iter()
|
||||
.map(|p| Coord { x: p.lon, y: p.lat })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let polygon = Polygon::new(LineString::from(ring), vec![]);
|
||||
let point = geo::Point::new(p.lon, p.lat);
|
||||
|
||||
polygon.contains(&point)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn hamburg_polygon() -> &'static [Point] {
|
||||
&[
|
||||
Point {
|
||||
lat: 53.63975308945899,
|
||||
lon: 9.764785766601562,
|
||||
},
|
||||
Point {
|
||||
lat: 53.59494998253459,
|
||||
lon: 9.827270507812,
|
||||
},
|
||||
Point {
|
||||
lat: 53.663153974456456,
|
||||
lon: 9.9151611328125,
|
||||
},
|
||||
Point {
|
||||
lat: 53.65582987649682,
|
||||
lon: 9.976272583007812,
|
||||
},
|
||||
Point {
|
||||
lat: 53.68613523817129,
|
||||
lon: 9.992752075195312,
|
||||
},
|
||||
Point {
|
||||
lat: 53.68674518938816,
|
||||
lon: 10.051460266113281,
|
||||
},
|
||||
Point {
|
||||
lat: 53.72495117617815,
|
||||
lon: 10.075492858886719,
|
||||
},
|
||||
Point {
|
||||
lat: 53.71946627930625,
|
||||
lon: 10.118408203125,
|
||||
},
|
||||
Point {
|
||||
lat: 53.743635083157756,
|
||||
lon: 10.164413452148438,
|
||||
},
|
||||
Point {
|
||||
lat: 53.73104466704585,
|
||||
lon: 10.202865600585938,
|
||||
},
|
||||
Point {
|
||||
lat: 53.676781546441546,
|
||||
lon: 10.16304016113281,
|
||||
},
|
||||
Point {
|
||||
lat: 53.632832079199474,
|
||||
lon: 10.235824584960938,
|
||||
},
|
||||
Point {
|
||||
lat: 53.608803292930894,
|
||||
lon: 10.2008056640625,
|
||||
},
|
||||
Point {
|
||||
lat: 53.578646152866504,
|
||||
lon: 10.208358764648438,
|
||||
},
|
||||
Point {
|
||||
lat: 53.57212285981298,
|
||||
lon: 10.163726806640625,
|
||||
},
|
||||
Point {
|
||||
lat: 53.52071674896369,
|
||||
lon: 10.18707275390625,
|
||||
},
|
||||
Point {
|
||||
lat: 53.52643162253097,
|
||||
lon: 10.224151611328125,
|
||||
},
|
||||
Point {
|
||||
lat: 53.44062753992289,
|
||||
lon: 10.347747802734375,
|
||||
},
|
||||
Point {
|
||||
lat: 53.38824275010831,
|
||||
lon: 10.248870849609375,
|
||||
},
|
||||
Point {
|
||||
lat: 53.38824275010831,
|
||||
lon: 10.15960693359375,
|
||||
},
|
||||
Point {
|
||||
lat: 53.44635321212876,
|
||||
lon: 10.064849853515625,
|
||||
},
|
||||
Point {
|
||||
lat: 53.40595029739904,
|
||||
lon: 9.985198974609375,
|
||||
},
|
||||
Point {
|
||||
lat: 53.42385506057106,
|
||||
lon: 9.951210021972656,
|
||||
},
|
||||
Point {
|
||||
lat: 53.41843327091211,
|
||||
lon: 9.944171905517578,
|
||||
},
|
||||
Point {
|
||||
lat: 53.41812635648326,
|
||||
lon: 9.927349090576172,
|
||||
},
|
||||
Point {
|
||||
lat: 53.412294561442884,
|
||||
lon: 9.917736053466797,
|
||||
},
|
||||
Point {
|
||||
lat: 53.41464783813818,
|
||||
lon: 9.901256561279297,
|
||||
},
|
||||
Point {
|
||||
lat: 53.443490472483326,
|
||||
lon: 9.912586212158201,
|
||||
},
|
||||
Point {
|
||||
lat: 53.45177144115704,
|
||||
lon: 9.897651672363281,
|
||||
},
|
||||
Point {
|
||||
lat: 53.43633277935392,
|
||||
lon: 9.866924285888672,
|
||||
},
|
||||
Point {
|
||||
lat: 53.427639673754776,
|
||||
lon: 9.866409301757812,
|
||||
},
|
||||
Point {
|
||||
lat: 53.427639673754776,
|
||||
lon: 9.858856201171875,
|
||||
},
|
||||
Point {
|
||||
lat: 53.46710230573499,
|
||||
lon: 9.795513153076172,
|
||||
},
|
||||
Point {
|
||||
lat: 53.49039461941655,
|
||||
lon: 9.795341491699219,
|
||||
},
|
||||
Point {
|
||||
lat: 53.49029248806277,
|
||||
lon: 9.77903366088867,
|
||||
},
|
||||
Point {
|
||||
lat: 53.49856433088649,
|
||||
lon: 9.780235290527344,
|
||||
},
|
||||
Point {
|
||||
lat: 53.5078554643033,
|
||||
lon: 9.758434295654297,
|
||||
},
|
||||
Point {
|
||||
lat: 53.545407634092975,
|
||||
lon: 9.759807586669922,
|
||||
},
|
||||
Point {
|
||||
lat: 53.568147234570084,
|
||||
lon: 9.633293151855469,
|
||||
},
|
||||
Point {
|
||||
lat: 53.58802162343514,
|
||||
lon: 9.655780792236328,
|
||||
},
|
||||
Point {
|
||||
lat: 53.568351121879815,
|
||||
lon: 9.727706909179688,
|
||||
},
|
||||
Point {
|
||||
lat: 53.60921067445695,
|
||||
lon: 9.737663269042969,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_hamburg_contains_hamburg() {
|
||||
let contains = polygon_contains(
|
||||
hamburg_polygon(),
|
||||
&Point {
|
||||
lat: 53.551085,
|
||||
lon: 9.993682,
|
||||
},
|
||||
);
|
||||
assert!(contains);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_hamburg_not_contains_berlin() {
|
||||
let contains = polygon_contains(
|
||||
hamburg_polygon(),
|
||||
&Point {
|
||||
lat: 52.52437,
|
||||
lon: 13.41053,
|
||||
},
|
||||
);
|
||||
assert!(!contains);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_hamburg_not_contains_ny() {
|
||||
let contains = polygon_contains(
|
||||
hamburg_polygon(),
|
||||
&Point {
|
||||
lat: 40.726662,
|
||||
lon: -74.036677,
|
||||
},
|
||||
);
|
||||
assert!(!contains);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +1,65 @@
|
||||
use chrootable_https::Client;
|
||||
use crate::archive;
|
||||
use crate::errors::*;
|
||||
use crate::lazy::LazyInit;
|
||||
use crate::paths;
|
||||
use crate::worker;
|
||||
use maxminddb::{self, geoip2};
|
||||
use std::fmt;
|
||||
use std::fs::{self, File};
|
||||
use std::net::IpAddr;
|
||||
use std::path::Path;
|
||||
use std::io::Read;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub static GEOIP_CITY_URL: &str = "https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz";
|
||||
pub static GEOIP_ASN_URL: &str = "https://geolite.maxmind.com/download/geoip/database/GeoLite2-ASN.tar.gz";
|
||||
|
||||
pub mod models;
|
||||
use self::models::GeoLookup;
|
||||
use self::models::AsnLookup;
|
||||
|
||||
use self::models::GeoLookup;
|
||||
|
||||
pub trait Maxmind: Sized {
|
||||
fn archive_filename() -> &'static str;
|
||||
|
||||
fn archive_url() -> &'static str;
|
||||
fn filename() -> &'static str;
|
||||
|
||||
fn new(reader: maxminddb::Reader<Vec<u8>>) -> Self;
|
||||
|
||||
// TODO: refactor this to return Path
|
||||
fn cache_path() -> Result<String> {
|
||||
fn cache_path(cache_dir: &Path) -> Result<PathBuf> {
|
||||
// use system path if exists
|
||||
for path in &[
|
||||
// Archlinux
|
||||
"/usr/share/GeoIP/",
|
||||
// OpenBSD
|
||||
"/usr/local/share/examples/libmaxminddb/",
|
||||
// geoipupdate
|
||||
"/var/lib/GeoIP/",
|
||||
] {
|
||||
let path = Path::new(path);
|
||||
let path = path.join(Self::archive_filename());
|
||||
let path = path.join(Self::filename());
|
||||
|
||||
if path.exists() {
|
||||
let path = path.to_str()
|
||||
.ok_or_else(|| format_err!("Failed to decode path"))?;
|
||||
return Ok(path.to_string());
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
|
||||
// use cache path
|
||||
let path = paths::cache_dir()?
|
||||
.join(Self::archive_filename());
|
||||
let path = path.to_str()
|
||||
.ok_or_else(|| format_err!("Failed to decode path"))?;
|
||||
Ok(path.to_string())
|
||||
let path = cache_dir.join(Self::filename());
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn from_buf(buf: Vec<u8>) -> Result<Self> {
|
||||
let reader = maxminddb::Reader::from_source(buf)
|
||||
.context("Failed to read geoip database")?;
|
||||
let reader =
|
||||
maxminddb::Reader::from_source(buf).context("Failed to read geoip database")?;
|
||||
Ok(Self::new(reader))
|
||||
}
|
||||
|
||||
fn open(path: &str) -> Result<Self> {
|
||||
fn open(path: &Path) -> Result<Self> {
|
||||
let buf = fs::read(path)?;
|
||||
Self::from_buf(buf)
|
||||
}
|
||||
|
||||
fn open_reader() -> Result<MaxmindReader> {
|
||||
let path = Self::cache_path()?;
|
||||
MaxmindReader::open_path(path)
|
||||
}
|
||||
fn try_open_reader(cache_dir: &Path) -> Result<Option<MaxmindReader>> {
|
||||
let path = Self::cache_path(cache_dir)?;
|
||||
|
||||
fn open_or_download() -> Result<Self> {
|
||||
let path = Self::cache_path()?;
|
||||
|
||||
if File::open(&path).is_err() {
|
||||
worker::spawn_fn(&format!("Downloading {:?}", Self::archive_filename()), || {
|
||||
Self::download(&path, Self::archive_filename(), Self::archive_url())
|
||||
}, false)?;
|
||||
};
|
||||
|
||||
Self::open(&path)
|
||||
}
|
||||
|
||||
fn download<P: AsRef<Path>>(path: P, filter: &str, url: &str) -> Result<()> {
|
||||
debug!("Downloading {:?}...", url);
|
||||
let client = Client::with_system_resolver()?;
|
||||
let resp = client.get(url)
|
||||
.wait_for_response()
|
||||
.context("http request failed")?;
|
||||
debug!("Downloaded {} bytes", resp.body.len());
|
||||
archive::extract(&mut &resp.body[..], filter, path)?;
|
||||
Ok(())
|
||||
if path.exists() {
|
||||
let db = MaxmindReader::open_path(path)?;
|
||||
Ok(Some(db))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +70,7 @@ pub struct MaxmindReader {
|
||||
impl MaxmindReader {
|
||||
fn open_path<P: AsRef<Path>>(path: P) -> Result<MaxmindReader> {
|
||||
let reader = File::open(path)?;
|
||||
Ok(MaxmindReader {
|
||||
reader,
|
||||
})
|
||||
Ok(MaxmindReader { reader })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,20 +108,13 @@ impl fmt::Debug for GeoIP {
|
||||
|
||||
impl Maxmind for GeoIP {
|
||||
#[inline]
|
||||
fn archive_filename() -> &'static str {
|
||||
fn filename() -> &'static str {
|
||||
"GeoLite2-City.mmdb"
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn archive_url() -> &'static str {
|
||||
GEOIP_CITY_URL
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new(reader: maxminddb::Reader<Vec<u8>>) -> Self {
|
||||
GeoIP {
|
||||
reader
|
||||
}
|
||||
GeoIP { reader }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,20 +138,13 @@ impl fmt::Debug for AsnDB {
|
||||
|
||||
impl Maxmind for AsnDB {
|
||||
#[inline]
|
||||
fn archive_filename() -> &'static str {
|
||||
fn filename() -> &'static str {
|
||||
"GeoLite2-ASN.mmdb"
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn archive_url() -> &'static str {
|
||||
GEOIP_ASN_URL
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new(reader: maxminddb::Reader<Vec<u8>>) -> Self {
|
||||
AsnDB {
|
||||
reader
|
||||
}
|
||||
AsnDB { reader }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,22 +158,29 @@ impl AsnDB {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// You need geoip setup on your system to run this
|
||||
/*
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_geoip_lookup() {
|
||||
let ip = "1.1.1.1".parse().unwrap();
|
||||
let geoip = GeoIP::open_or_download().expect("Failed to load geoip");
|
||||
let path = GeoIP::cache_path().unwrap();
|
||||
let geoip = GeoIP::open(&path).unwrap();
|
||||
let lookup = geoip.lookup(ip).expect("GeoIP lookup failed");
|
||||
println!("{:#?}", lookup);
|
||||
assert_eq!(lookup.city, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_asn_lookup() {
|
||||
let ip = "1.1.1.1".parse().unwrap();
|
||||
let asndb = AsnDB::open_or_download().expect("Failed to load asndb");
|
||||
let path = AsnDB::cache_path().unwrap();
|
||||
let asndb = AsnDB::open(&path).unwrap();
|
||||
let lookup = asndb.lookup(ip).expect("ASN lookup failed");
|
||||
println!("{:#?}", lookup);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
use crate::errors::*;
|
||||
use maxminddb::geoip2;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
|
||||
fn from_geoip_model_names(names: Option<BTreeMap<String, String>>) -> Option<String> {
|
||||
let names = match names {
|
||||
Some(names) => names,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
names.get("en")
|
||||
.map(|x| x.to_owned())
|
||||
fn from_geoip_model_names(names: Option<BTreeMap<&str, &str>>) -> Option<String> {
|
||||
names?.get("en").map(|x| x.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -24,7 +18,7 @@ pub struct GeoLookup {
|
||||
pub longitude: Option<f64>,
|
||||
}
|
||||
|
||||
impl From<geoip2::City> for GeoLookup {
|
||||
impl<'a> From<geoip2::City<'a>> for GeoLookup {
|
||||
fn from(lookup: geoip2::City) -> GeoLookup {
|
||||
// parse maxminddb lookup
|
||||
let continent = match lookup.continent {
|
||||
@@ -78,18 +72,12 @@ pub struct Continent {
|
||||
}
|
||||
|
||||
impl Continent {
|
||||
fn from_maxmind(continent: geoip2::model::Continent) -> Option<Self> {
|
||||
let code = match continent.code {
|
||||
Some(code) => code,
|
||||
_ => return None,
|
||||
};
|
||||
let name = match from_geoip_model_names(continent.names) {
|
||||
Some(name) => name,
|
||||
_ => return None,
|
||||
};
|
||||
fn from_maxmind(continent: geoip2::city::Continent) -> Option<Self> {
|
||||
let code = continent.code?;
|
||||
let name = from_geoip_model_names(continent.names)?;
|
||||
|
||||
Some(Continent {
|
||||
code,
|
||||
code: code.to_string(),
|
||||
name,
|
||||
})
|
||||
}
|
||||
@@ -102,18 +90,12 @@ pub struct Country {
|
||||
}
|
||||
|
||||
impl Country {
|
||||
fn from_maxmind(country: geoip2::model::Country) -> Option<Self> {
|
||||
let code = match country.iso_code {
|
||||
Some(code) => code,
|
||||
_ => return None,
|
||||
};
|
||||
let name = match from_geoip_model_names(country.names) {
|
||||
Some(name) => name,
|
||||
_ => return None,
|
||||
};
|
||||
fn from_maxmind(country: geoip2::city::Country) -> Option<Self> {
|
||||
let code = country.iso_code?;
|
||||
let name = from_geoip_model_names(country.names)?;
|
||||
|
||||
Some(Country {
|
||||
code,
|
||||
code: code.to_string(),
|
||||
name,
|
||||
})
|
||||
}
|
||||
@@ -126,7 +108,7 @@ pub struct Location {
|
||||
}
|
||||
|
||||
impl Location {
|
||||
fn from_maxmind(location: &geoip2::model::Location) -> Option<Self> {
|
||||
fn from_maxmind(location: &geoip2::city::Location) -> Option<Self> {
|
||||
let latitude = match location.latitude {
|
||||
Some(latitude) => latitude,
|
||||
_ => return None,
|
||||
@@ -152,19 +134,16 @@ pub struct AsnLookup {
|
||||
impl AsnLookup {
|
||||
pub fn try_from(lookup: geoip2::Isp) -> Result<AsnLookup> {
|
||||
// parse maxminddb lookup
|
||||
let asn = match lookup.autonomous_system_number {
|
||||
Some(asn) => asn,
|
||||
_ => bail!("autonomous_system_number not set"),
|
||||
};
|
||||
let as_org = match lookup.autonomous_system_organization {
|
||||
Some(org) => org,
|
||||
_ => bail!("autonomous_system_organization not set"),
|
||||
};
|
||||
let asn = lookup
|
||||
.autonomous_system_number
|
||||
.ok_or_else(|| format_err!("autonomous_system_number not set"))?;
|
||||
let as_org = lookup
|
||||
.autonomous_system_organization
|
||||
.ok_or_else(|| format_err!("autonomous_system_organization not set"))?;
|
||||
|
||||
// return result
|
||||
Ok(AsnLookup {
|
||||
asn,
|
||||
as_org,
|
||||
as_org: as_org.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
use crate::errors::*;
|
||||
|
||||
use exif;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
latitude: f64,
|
||||
@@ -11,11 +9,12 @@ pub struct Location {
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn try_from(fields: &[exif::Field]) -> Result<Location> {
|
||||
fn try_from_iter<'a, I: IntoIterator<Item = &'a exif::Field>>(iter: I) -> Result<Self> {
|
||||
let mut builder = LocationBuilder::default();
|
||||
fields.iter()
|
||||
.map(|f| builder.add_one(f))
|
||||
.collect::<Result<()>>()?;
|
||||
for f in iter {
|
||||
debug!("Exif field: {:?}", f.display_value().to_string());
|
||||
builder.add_one(f)?;
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
@@ -32,8 +31,12 @@ impl LocationBuilder {
|
||||
fn add_one(&mut self, f: &exif::Field) -> Result<()> {
|
||||
debug!("Exif tag: {:?}, {}", f.tag, f.value.display_as(f.tag));
|
||||
match f.tag {
|
||||
exif::Tag::GPSLatitudeRef => self.latitude_ref = Some(cardinal_direction_modifier(&f.value)?),
|
||||
exif::Tag::GPSLongitudeRef => self.longitude_ref = Some(cardinal_direction_modifier(&f.value)?),
|
||||
exif::Tag::GPSLatitudeRef => {
|
||||
self.latitude_ref = Some(cardinal_direction_modifier(&f.value)?)
|
||||
}
|
||||
exif::Tag::GPSLongitudeRef => {
|
||||
self.longitude_ref = Some(cardinal_direction_modifier(&f.value)?)
|
||||
}
|
||||
exif::Tag::GPSLatitude => self.latitude = Some(dms_to_float(&f.value)?),
|
||||
exif::Tag::GPSLongitude => self.longitude = Some(dms_to_float(&f.value)?),
|
||||
_ => (),
|
||||
@@ -42,14 +45,18 @@ impl LocationBuilder {
|
||||
}
|
||||
|
||||
fn build(self) -> Result<Location> {
|
||||
let latitude = self.latitude
|
||||
let latitude = self
|
||||
.latitude
|
||||
.ok_or_else(|| format_err!("Missing latitude field"))?;
|
||||
let latitude_ref = self.latitude_ref
|
||||
let latitude_ref = self
|
||||
.latitude_ref
|
||||
.ok_or_else(|| format_err!("Missing latitude field"))?;
|
||||
|
||||
let longitude = self.longitude
|
||||
let longitude = self
|
||||
.longitude
|
||||
.ok_or_else(|| format_err!("Missing latitude field"))?;
|
||||
let longitude_ref = self.longitude_ref
|
||||
let longitude_ref = self
|
||||
.longitude_ref
|
||||
.ok_or_else(|| format_err!("Missing latitude field"))?;
|
||||
|
||||
Ok(Location {
|
||||
@@ -60,12 +67,11 @@ impl LocationBuilder {
|
||||
}
|
||||
|
||||
pub fn gps(img: &[u8]) -> Result<Option<Location>> {
|
||||
let mut buf = io::BufReader::new(img);
|
||||
let reader = exif::Reader::new(&mut buf)?;
|
||||
let mut buf = io::Cursor::new(img);
|
||||
let reader = exif::Reader::new().read_from_container(&mut buf)?;
|
||||
let fields = reader.fields();
|
||||
debug!("Exif fields: {:?}", fields);
|
||||
|
||||
let location = Location::try_from(fields).ok();
|
||||
let location = Location::try_from_iter(fields).ok();
|
||||
Ok(location)
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ pub fn dms_to_float(dms: &exif::Value) -> Result<f64> {
|
||||
let minutes = dms[1].to_f64();
|
||||
let seconds = dms[2].to_f64();
|
||||
|
||||
let float = degrees + minutes/60.0 + seconds/3600.0;
|
||||
let float = degrees + minutes / 60.0 + seconds / 3600.0;
|
||||
let float = (float * 1000000.0).round() / 1000000.0;
|
||||
Ok(float)
|
||||
}
|
||||
@@ -91,17 +97,18 @@ pub fn dms_to_float(dms: &exif::Value) -> Result<f64> {
|
||||
pub fn cardinal_direction_modifier(value: &exif::Value) -> Result<f64> {
|
||||
match value {
|
||||
exif::Value::Ascii(s) => {
|
||||
let s = s.get(0)
|
||||
let s = s
|
||||
.get(0)
|
||||
.ok_or_else(|| format_err!("Cardinal direction value is empty"))?;
|
||||
|
||||
match s.get(0) {
|
||||
match s.first() {
|
||||
Some(b'N') => Ok(1.0),
|
||||
Some(b'S') => Ok(-1.0),
|
||||
Some(b'E') => Ok(1.0),
|
||||
Some(b'W') => Ok(-1.0),
|
||||
_ => bail!("Unexpected cardinal direction"),
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => bail!("Unexpected exif value"),
|
||||
}
|
||||
}
|
||||
@@ -115,65 +122,67 @@ mod tests {
|
||||
fn verify_exif_location() {
|
||||
test_init();
|
||||
|
||||
let location = Location::try_from(&[
|
||||
let location = Location::try_from_iter(&[
|
||||
exif::Field {
|
||||
tag: exif::Tag::GPSLatitudeRef,
|
||||
thumbnail: false,
|
||||
value: exif::Value::Ascii(vec![&[b'N']]),
|
||||
}, exif::Field {
|
||||
tag: exif::Tag::GPSLongitudeRef,
|
||||
thumbnail: false,
|
||||
value: exif::Value::Ascii(vec![&[b'E']]),
|
||||
}, exif::Field {
|
||||
tag: exif::Tag::GPSLatitude,
|
||||
thumbnail: false,
|
||||
value: exif::Value::Rational(vec![exif::Rational {
|
||||
num: 43,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 28,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 176399999,
|
||||
denom: 100000000,
|
||||
}]),
|
||||
}, exif::Field {
|
||||
tag: exif::Tag::GPSLongitude,
|
||||
thumbnail: false,
|
||||
value: exif::Value::Rational(vec![exif::Rational {
|
||||
num: 11,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 53,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 742199999,
|
||||
denom: 100000000,
|
||||
}]),
|
||||
ifd_num: exif::In::PRIMARY,
|
||||
value: exif::Value::Ascii(vec![vec![b'N']]),
|
||||
},
|
||||
]).unwrap();
|
||||
exif::Field {
|
||||
tag: exif::Tag::GPSLongitudeRef,
|
||||
ifd_num: exif::In::PRIMARY,
|
||||
value: exif::Value::Ascii(vec![vec![b'E']]),
|
||||
},
|
||||
exif::Field {
|
||||
tag: exif::Tag::GPSLatitude,
|
||||
ifd_num: exif::In::PRIMARY,
|
||||
value: exif::Value::Rational(vec![
|
||||
exif::Rational { num: 43, denom: 1 },
|
||||
exif::Rational { num: 28, denom: 1 },
|
||||
exif::Rational {
|
||||
num: 176399999,
|
||||
denom: 100000000,
|
||||
},
|
||||
]),
|
||||
},
|
||||
exif::Field {
|
||||
tag: exif::Tag::GPSLongitude,
|
||||
ifd_num: exif::In::PRIMARY,
|
||||
value: exif::Value::Rational(vec![
|
||||
exif::Rational { num: 11, denom: 1 },
|
||||
exif::Rational { num: 53, denom: 1 },
|
||||
exif::Rational {
|
||||
num: 742199999,
|
||||
denom: 100000000,
|
||||
},
|
||||
]),
|
||||
},
|
||||
])
|
||||
.unwrap();
|
||||
println!("{:?}", location);
|
||||
|
||||
assert_eq!(location, Location {
|
||||
latitude: 43.467157,
|
||||
longitude: 11.885395
|
||||
});
|
||||
assert_eq!(
|
||||
location,
|
||||
Location {
|
||||
latitude: 43.467157,
|
||||
longitude: 11.885395
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_dms() {
|
||||
test_init();
|
||||
|
||||
let latitude = dms_to_float(&exif::Value::Rational(vec![exif::Rational {
|
||||
num: 43,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 28,
|
||||
denom: 1,
|
||||
}, exif::Rational {
|
||||
num: 176399999,
|
||||
denom: 100000000,
|
||||
}])).unwrap();
|
||||
let latitude = dms_to_float(&exif::Value::Rational(vec![
|
||||
exif::Rational { num: 43, denom: 1 },
|
||||
exif::Rational { num: 28, denom: 1 },
|
||||
exif::Rational {
|
||||
num: 176399999,
|
||||
denom: 100000000,
|
||||
},
|
||||
]))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(latitude, 43.467157);
|
||||
}
|
||||
127
sn0int-std/src/gfx/mod.rs
Normal file
127
sn0int-std/src/gfx/mod.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use crate::errors::*;
|
||||
use image::{self, DynamicImage, GenericImageView};
|
||||
pub use img_hash_median::HashAlg;
|
||||
|
||||
pub mod exif;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImageFormat {
|
||||
Png,
|
||||
Jpeg,
|
||||
Gif,
|
||||
WebP,
|
||||
Tiff,
|
||||
Bmp,
|
||||
Ico,
|
||||
}
|
||||
|
||||
impl ImageFormat {
|
||||
pub fn mime(&self) -> &str {
|
||||
// https://www.iana.org/assignments/media-types/media-types.xhtml#image
|
||||
// /etc/nginx/mime.types
|
||||
match self {
|
||||
ImageFormat::Png => "image/png",
|
||||
ImageFormat::Jpeg => "image/jpeg",
|
||||
ImageFormat::Gif => "image/gif",
|
||||
ImageFormat::WebP => "image/webp",
|
||||
ImageFormat::Tiff => "image/tiff",
|
||||
ImageFormat::Bmp => "image/bmp",
|
||||
ImageFormat::Ico => "image/vnd.microsoft.icon",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from(format: &image::ImageFormat) -> Result<ImageFormat> {
|
||||
use image::ImageFormat::*;
|
||||
match format {
|
||||
Png => Ok(ImageFormat::Png),
|
||||
Jpeg => Ok(ImageFormat::Jpeg),
|
||||
Gif => Ok(ImageFormat::Gif),
|
||||
WebP => Ok(ImageFormat::WebP),
|
||||
Tiff => Ok(ImageFormat::Tiff),
|
||||
Bmp => Ok(ImageFormat::Bmp),
|
||||
Ico => Ok(ImageFormat::Ico),
|
||||
_ => bail!("Unsupported format: {:?}", format),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageFormat> for image::ImageFormat {
|
||||
fn from(format: ImageFormat) -> image::ImageFormat {
|
||||
match format {
|
||||
ImageFormat::Png => image::ImageFormat::Png,
|
||||
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
|
||||
ImageFormat::Gif => image::ImageFormat::Gif,
|
||||
ImageFormat::WebP => image::ImageFormat::WebP,
|
||||
ImageFormat::Tiff => image::ImageFormat::Tiff,
|
||||
ImageFormat::Bmp => image::ImageFormat::Bmp,
|
||||
ImageFormat::Ico => image::ImageFormat::Ico,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Image {
|
||||
image: DynamicImage,
|
||||
format: ImageFormat,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
#[inline]
|
||||
pub fn mime(&self) -> &str {
|
||||
self.format.mime()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> u32 {
|
||||
self.image.width()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> u32 {
|
||||
self.image.height()
|
||||
}
|
||||
|
||||
pub fn perception_hash(&self, hash_alg: HashAlg) -> String {
|
||||
let hasher = img_hash_median::HasherConfig::new()
|
||||
.hash_alg(hash_alg)
|
||||
.to_hasher();
|
||||
let hash = hasher.hash_image(&self.image);
|
||||
hash.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<DynamicImage> for Image {
|
||||
fn as_ref(&self) -> &DynamicImage {
|
||||
&self.image
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn guess_format(buf: &[u8]) -> Result<ImageFormat> {
|
||||
let format = image::guess_format(buf)?;
|
||||
ImageFormat::try_from(&format)
|
||||
}
|
||||
|
||||
pub fn load(buf: &[u8]) -> Result<Image> {
|
||||
let img_format = image::guess_format(buf)?;
|
||||
let format = ImageFormat::try_from(&img_format)?;
|
||||
|
||||
let image = image::load_from_memory_with_format(buf, img_format)?;
|
||||
|
||||
Ok(Image { image, format })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn verify_gfx_load_ico() {
|
||||
let ico = fs::read("../sn0int-registry/assets/favicon.ico").expect("fs::read");
|
||||
let img = load(&ico).expect("gfx::load");
|
||||
|
||||
assert_eq!("image/vnd.microsoft.icon", img.mime());
|
||||
assert_eq!(16, img.height());
|
||||
assert_eq!(16, img.width());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
use crate::errors::Result;
|
||||
|
||||
use kuchiki;
|
||||
use crate::engine::structs::LuaMap;
|
||||
use crate::errors::*;
|
||||
use crate::hlua::AnyLuaValue;
|
||||
use kuchiki::traits::TendrilSink;
|
||||
use std::collections::HashMap;
|
||||
use crate::hlua::AnyLuaValue;
|
||||
use crate::engine::structs::LuaMap;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Element {
|
||||
@@ -14,13 +11,13 @@ pub struct Element {
|
||||
pub html: String,
|
||||
}
|
||||
|
||||
impl Into<AnyLuaValue> for Element {
|
||||
fn into(self) -> AnyLuaValue {
|
||||
impl From<Element> for AnyLuaValue {
|
||||
fn from(elem: Element) -> AnyLuaValue {
|
||||
let mut map = LuaMap::new();
|
||||
|
||||
map.insert_str("text", self.text);
|
||||
map.insert("attrs", LuaMap::from(self.attrs));
|
||||
map.insert_str("html", self.html);
|
||||
map.insert_str("text", elem.text);
|
||||
map.insert("attrs", LuaMap::from(elem.attrs));
|
||||
map.insert_str("html", elem.html);
|
||||
|
||||
map.into()
|
||||
}
|
||||
@@ -44,14 +41,10 @@ fn transform_element(entry: &kuchiki::NodeDataRef<kuchiki::ElementData>) -> Elem
|
||||
Err(_) => {
|
||||
debug!("html serialize failed");
|
||||
String::new()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Element {
|
||||
attrs,
|
||||
text,
|
||||
html,
|
||||
}
|
||||
Element { attrs, text, html }
|
||||
}
|
||||
|
||||
pub fn html_select(html: &str, selector: &str) -> Result<Element> {
|
||||
@@ -98,17 +91,18 @@ pub fn html_form(html: &str) -> Result<HashMap<String, String>> {
|
||||
Ok(form)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use maplit::hashmap;
|
||||
|
||||
#[test]
|
||||
fn test_html_select() {
|
||||
let elems = html_select(r#"<html><div id="yey">content</div></html>"#, "#yey").unwrap();
|
||||
assert_eq!(elems,
|
||||
assert_eq!(
|
||||
elems,
|
||||
Element {
|
||||
attrs: hashmap!{
|
||||
attrs: hashmap! {
|
||||
"id".into() => "yey".into(),
|
||||
},
|
||||
text: "content".into(),
|
||||
@@ -119,15 +113,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_html_select_list() {
|
||||
let elems = html_select_list(r#"<html><div id="yey">content</div></html>"#, "#yey").unwrap();
|
||||
assert_eq!(elems, vec![
|
||||
Element {
|
||||
attrs: hashmap!{
|
||||
let elems =
|
||||
html_select_list(r#"<html><div id="yey">content</div></html>"#, "#yey").unwrap();
|
||||
assert_eq!(
|
||||
elems,
|
||||
vec![Element {
|
||||
attrs: hashmap! {
|
||||
"id".into() => "yey".into(),
|
||||
},
|
||||
text: "content".into(),
|
||||
html: r#"<div id="yey">content</div>"#.into(),
|
||||
}
|
||||
]);
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
use crate::errors::*;
|
||||
|
||||
use std::iter::FromIterator;
|
||||
use std::collections::HashMap;
|
||||
use crate::hlua::AnyLuaValue;
|
||||
use serde_json::{self, Deserializer, Value, Number, Map};
|
||||
|
||||
use serde_json::{self, Deserializer, Map, Number, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn decode<T: AsRef<[u8]>>(x: T) -> Result<AnyLuaValue> {
|
||||
let v: Value = serde_json::from_slice(x.as_ref())
|
||||
.context("deserialize failed")?;
|
||||
let v: Value = serde_json::from_slice(x.as_ref()).context("deserialize failed")?;
|
||||
let v: LuaJsonValue = v.into();
|
||||
Ok(v.into())
|
||||
}
|
||||
@@ -16,13 +13,12 @@ pub fn decode<T: AsRef<[u8]>>(x: T) -> Result<AnyLuaValue> {
|
||||
pub fn encode(v: AnyLuaValue) -> Result<String> {
|
||||
let v: LuaJsonValue = v.into();
|
||||
let v: Value = v.into();
|
||||
let s = serde_json::to_string(&v)
|
||||
.context("serialize failed")?;
|
||||
let s = serde_json::to_string(&v).context("serialize failed")?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn decode_stream(x: &str) -> Result<Vec<AnyLuaValue>> {
|
||||
let stream = Deserializer::from_str(&x).into_iter::<Value>();
|
||||
let stream = Deserializer::from_str(x).into_iter::<Value>();
|
||||
|
||||
let list = stream
|
||||
.filter_map(|x| x.ok())
|
||||
@@ -35,11 +31,7 @@ pub fn decode_stream(x: &str) -> Result<Vec<AnyLuaValue>> {
|
||||
pub fn lua_array_is_list(array: &[(AnyLuaValue, AnyLuaValue)]) -> bool {
|
||||
if !array.is_empty() {
|
||||
let first = &array[0];
|
||||
if let AnyLuaValue::LuaNumber(_) = first.0 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(first.0, AnyLuaValue::LuaNumber(_))
|
||||
} else {
|
||||
// true // TODO: this breaks unserialize
|
||||
false
|
||||
@@ -56,21 +48,24 @@ pub enum LuaJsonValue {
|
||||
Object(HashMap<String, LuaJsonValue>),
|
||||
}
|
||||
|
||||
impl Into<AnyLuaValue> for LuaJsonValue {
|
||||
fn into(self) -> AnyLuaValue {
|
||||
match self {
|
||||
impl From<LuaJsonValue> for AnyLuaValue {
|
||||
fn from(value: LuaJsonValue) -> AnyLuaValue {
|
||||
match value {
|
||||
LuaJsonValue::Null => AnyLuaValue::LuaNil,
|
||||
LuaJsonValue::Bool(v) => AnyLuaValue::LuaBoolean(v),
|
||||
// TODO: not sure if this might fail
|
||||
LuaJsonValue::Number(v) => AnyLuaValue::LuaNumber(v.as_f64().unwrap()),
|
||||
LuaJsonValue::String(v) => AnyLuaValue::LuaString(v),
|
||||
LuaJsonValue::Array(v) => AnyLuaValue::LuaArray(v.into_iter().enumerate()
|
||||
.map(|(i, x)| (AnyLuaValue::LuaNumber((i+1) as f64), x.into()))
|
||||
.collect()
|
||||
LuaJsonValue::Array(v) => AnyLuaValue::LuaArray(
|
||||
v.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| (AnyLuaValue::LuaNumber((i + 1) as f64), x.into()))
|
||||
.collect(),
|
||||
),
|
||||
LuaJsonValue::Object(v) => AnyLuaValue::LuaArray(v.into_iter()
|
||||
.map(|(k, v)| (AnyLuaValue::LuaString(k), v.into()))
|
||||
.collect()
|
||||
LuaJsonValue::Object(v) => AnyLuaValue::LuaArray(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (AnyLuaValue::LuaString(k), v.into()))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -82,9 +77,10 @@ impl From<AnyLuaValue> for LuaJsonValue {
|
||||
AnyLuaValue::LuaNil => LuaJsonValue::Null,
|
||||
AnyLuaValue::LuaBoolean(v) => LuaJsonValue::Bool(v),
|
||||
AnyLuaValue::LuaString(v) => LuaJsonValue::String(v),
|
||||
AnyLuaValue::LuaAnyString(v) => LuaJsonValue::Array(v.0.into_iter()
|
||||
.map(|x| LuaJsonValue::Number(x.into()))
|
||||
.collect()
|
||||
AnyLuaValue::LuaAnyString(v) => LuaJsonValue::Array(
|
||||
v.0.into_iter()
|
||||
.map(|x| LuaJsonValue::Number(x.into()))
|
||||
.collect(),
|
||||
),
|
||||
AnyLuaValue::LuaNumber(v) => {
|
||||
// this is needed or every number is detected as float
|
||||
@@ -93,42 +89,39 @@ impl From<AnyLuaValue> for LuaJsonValue {
|
||||
} else {
|
||||
Number::from_f64(v).expect("invalid LuaJson::Number")
|
||||
})
|
||||
},
|
||||
}
|
||||
AnyLuaValue::LuaArray(v) => {
|
||||
if lua_array_is_list(&v) {
|
||||
LuaJsonValue::Array(v.into_iter()
|
||||
.map(|(_, v)| v.into())
|
||||
.collect()
|
||||
)
|
||||
LuaJsonValue::Array(v.into_iter().map(|(_, v)| v.into()).collect())
|
||||
} else {
|
||||
LuaJsonValue::Object(v.into_iter()
|
||||
.filter_map(|(k, v)| match k {
|
||||
AnyLuaValue::LuaString(k) => Some((k, v.into())),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
LuaJsonValue::Object(
|
||||
v.into_iter()
|
||||
.filter_map(|(k, v)| match k {
|
||||
AnyLuaValue::LuaString(k) => Some((k, v.into())),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
AnyLuaValue::LuaOther => LuaJsonValue::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<serde_json::Value> for LuaJsonValue {
|
||||
fn into(self) -> serde_json::Value {
|
||||
match self {
|
||||
impl From<LuaJsonValue> for serde_json::Value {
|
||||
fn from(value: LuaJsonValue) -> serde_json::Value {
|
||||
match value {
|
||||
LuaJsonValue::Null => Value::Null,
|
||||
LuaJsonValue::Bool(v) => Value::Bool(v),
|
||||
LuaJsonValue::Number(v) => Value::Number(v),
|
||||
LuaJsonValue::String(v) => Value::String(v),
|
||||
LuaJsonValue::Array(v) => Value::Array(v.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect()
|
||||
LuaJsonValue::Array(v) => Value::Array(v.into_iter().map(|x| x.into()).collect()),
|
||||
LuaJsonValue::Object(v) => Value::Object(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect::<Map<_, _>>(),
|
||||
),
|
||||
LuaJsonValue::Object(v) => Value::Object(Map::from_iter(v.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,13 +133,12 @@ impl From<serde_json::Value> for LuaJsonValue {
|
||||
Value::Bool(v) => LuaJsonValue::Bool(v),
|
||||
Value::Number(v) => LuaJsonValue::Number(v),
|
||||
Value::String(v) => LuaJsonValue::String(v),
|
||||
Value::Array(v) => LuaJsonValue::Array(v.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect()
|
||||
Value::Array(v) => LuaJsonValue::Array(v.into_iter().map(|x| x.into()).collect()),
|
||||
Value::Object(v) => LuaJsonValue::Object(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
),
|
||||
Value::Object(v) => LuaJsonValue::Object(HashMap::from_iter(v.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,12 @@ impl<T1: LazyInit<T2>, T2> Lazy<T1, T2> {
|
||||
pub fn get(&mut self) -> Result<&mut T2> {
|
||||
match self {
|
||||
Lazy::Init(init) => {
|
||||
let init = init.take()
|
||||
let init = init
|
||||
.take()
|
||||
.ok_or_else(|| format_err!("Previous initialization failed"))?;
|
||||
*self = Lazy::Active(init.initialize()?);
|
||||
self.get()
|
||||
},
|
||||
}
|
||||
Lazy::Active(active) => Ok(active),
|
||||
}
|
||||
}
|
||||
25
sn0int-std/src/lib.rs
Normal file
25
sn0int-std/src/lib.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use hlua_badtouch as hlua;
|
||||
|
||||
pub mod blobs;
|
||||
pub mod crt;
|
||||
pub mod crypto;
|
||||
pub mod engine;
|
||||
mod errors;
|
||||
pub mod geo;
|
||||
pub mod geoip;
|
||||
pub mod gfx;
|
||||
pub mod html;
|
||||
pub mod json;
|
||||
pub mod lazy;
|
||||
pub mod mqtt;
|
||||
pub mod psl;
|
||||
pub mod ratelimits;
|
||||
pub mod sockets;
|
||||
pub mod web;
|
||||
pub mod websockets;
|
||||
pub mod xml;
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_init() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
}
|
||||
294
sn0int-std/src/mqtt.rs
Normal file
294
sn0int-std/src/mqtt.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
use crate::errors::*;
|
||||
use crate::hlua::AnyLuaValue;
|
||||
use crate::json::LuaJsonValue;
|
||||
use crate::sockets::{SocketOptions, Stream};
|
||||
use chrootable_https::DnsResolver;
|
||||
use mqtt::control::fixed_header::FixedHeaderError;
|
||||
use mqtt::control::ConnectReturnCode;
|
||||
use mqtt::encodable::{Decodable, Encodable};
|
||||
use mqtt::packet::VariablePacketError;
|
||||
use mqtt::packet::{ConnectPacket, PingreqPacket, SubscribePacket, VariablePacket};
|
||||
use mqtt::{QualityOfService, TopicFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::{Duration, Instant};
|
||||
use url::Url;
|
||||
|
||||
// a reasonable default for keep-alive
|
||||
// some servers reject 0 as invalid with a very confusing error message
|
||||
const DEFAULT_PING_INTERVAL: u64 = 90;
|
||||
const DEFAULT_KEEP_ALIVE: u16 = 120;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct MqttOptions {
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
|
||||
pub proxy: Option<SocketAddr>,
|
||||
#[serde(default)]
|
||||
pub connect_timeout: u64,
|
||||
pub read_timeout: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub write_timeout: u64,
|
||||
|
||||
pub ping_interval: Option<u64>,
|
||||
pub keep_alive: Option<u16>,
|
||||
}
|
||||
|
||||
impl MqttOptions {
|
||||
pub fn try_from(x: AnyLuaValue) -> Result<MqttOptions> {
|
||||
let x = LuaJsonValue::from(x);
|
||||
let x = serde_json::from_value(x.into())?;
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MqttRecvError {
|
||||
#[error("Failed to read mqtt packet: {0:#}")]
|
||||
Recv(#[from] VariablePacketError),
|
||||
#[error("Failed to read mqtt packet: connection disconnected")]
|
||||
RecvDisconnect,
|
||||
#[error("Failed to interact with mqtt: {0:#}")]
|
||||
Error(Error),
|
||||
}
|
||||
|
||||
impl From<Error> for MqttRecvError {
|
||||
fn from(err: Error) -> Self {
|
||||
MqttRecvError::Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MqttClient {
|
||||
stream: Stream,
|
||||
last_ping: Instant,
|
||||
ping_interval: Option<u64>,
|
||||
}
|
||||
|
||||
impl MqttClient {
|
||||
pub fn negotiate(stream: Stream, options: &MqttOptions) -> Result<MqttClient> {
|
||||
// default to DEFAULT_PING_INTERVAL, if an explicit value of 0 was set, disable auto-ping
|
||||
let ping_interval = Some(options.ping_interval.unwrap_or(DEFAULT_PING_INTERVAL));
|
||||
ping_interval.filter(|s| *s != 0);
|
||||
|
||||
let mut client = MqttClient {
|
||||
stream,
|
||||
last_ping: Instant::now(),
|
||||
ping_interval,
|
||||
};
|
||||
|
||||
let mut pkt = ConnectPacket::new("sn0int");
|
||||
pkt.set_user_name(options.username.clone());
|
||||
pkt.set_password(options.password.clone());
|
||||
pkt.set_keep_alive(options.keep_alive.unwrap_or(DEFAULT_KEEP_ALIVE));
|
||||
|
||||
client.send(pkt.into())?;
|
||||
let pkt = client.recv()?;
|
||||
|
||||
if let VariablePacket::ConnackPacket(pkt) = pkt {
|
||||
let code = pkt.connect_return_code();
|
||||
if code == ConnectReturnCode::ConnectionAccepted {
|
||||
Ok(client)
|
||||
} else {
|
||||
bail!("MQTT negotiation failed: {:?}", code);
|
||||
}
|
||||
} else {
|
||||
bail!("Expected ConnAck, received {:?}", pkt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect<R: DnsResolver>(
|
||||
resolver: &R,
|
||||
url: Url,
|
||||
options: &MqttOptions,
|
||||
) -> Result<MqttClient> {
|
||||
let tls = match url.scheme() {
|
||||
"mqtt" => false,
|
||||
"mqtts" => true,
|
||||
_ => bail!("Invalid mqtt protocol"),
|
||||
};
|
||||
|
||||
let host = url
|
||||
.host_str()
|
||||
.ok_or_else(|| format_err!("Missing host in url"))?;
|
||||
|
||||
let port = match (url.port(), tls) {
|
||||
(Some(port), _) => port,
|
||||
(None, true) => 8883,
|
||||
(None, false) => 1883,
|
||||
};
|
||||
|
||||
// if no read timeout is configured then keep alive won't work
|
||||
let read_timeout = options.read_timeout.unwrap_or(DEFAULT_PING_INTERVAL);
|
||||
|
||||
let stream = Stream::connect_stream(
|
||||
resolver,
|
||||
host,
|
||||
port,
|
||||
&SocketOptions {
|
||||
tls,
|
||||
sni_value: None,
|
||||
disable_tls_verify: false,
|
||||
proxy: options.proxy,
|
||||
|
||||
connect_timeout: options.connect_timeout,
|
||||
read_timeout,
|
||||
write_timeout: options.write_timeout,
|
||||
},
|
||||
)?;
|
||||
|
||||
Self::negotiate(stream, options)
|
||||
}
|
||||
|
||||
fn maintain_ping(&mut self) -> Result<()> {
|
||||
if let Some(ping_interval) = self.ping_interval {
|
||||
if self.last_ping.elapsed() >= Duration::from_secs(ping_interval) {
|
||||
self.ping().context("Failed to ping")?;
|
||||
self.last_ping = Instant::now();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send(&mut self, pkt: VariablePacket) -> Result<()> {
|
||||
self.maintain_ping()?;
|
||||
debug!("Sending mqtt packet: {:?}", pkt);
|
||||
pkt.encode(&mut self.stream)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv(&mut self) -> std::result::Result<VariablePacket, MqttRecvError> {
|
||||
self.maintain_ping()?;
|
||||
let pkt = VariablePacket::decode(&mut self.stream).map_err(|err| match err {
|
||||
// search for any io error and check if it's ErrorKind::UnexpectedEof
|
||||
VariablePacketError::IoError(err)
|
||||
| VariablePacketError::FixedHeaderError(FixedHeaderError::IoError(err))
|
||||
if err.kind() == io::ErrorKind::UnexpectedEof =>
|
||||
{
|
||||
MqttRecvError::RecvDisconnect
|
||||
}
|
||||
_ => MqttRecvError::Recv(err),
|
||||
})?;
|
||||
debug!("Received mqtt packet: {:?}", pkt);
|
||||
Ok(pkt)
|
||||
}
|
||||
|
||||
pub fn subscribe(&mut self, topic: &str, qos: u8) -> Result<()> {
|
||||
let filter = TopicFilter::new(topic)?;
|
||||
|
||||
let qos = match qos {
|
||||
0 => QualityOfService::Level0,
|
||||
1 => QualityOfService::Level1,
|
||||
2 => QualityOfService::Level2,
|
||||
_ => bail!("Invalid QoS level: {}", qos),
|
||||
};
|
||||
|
||||
let pkt = SubscribePacket::new(1, vec![(filter, qos)]);
|
||||
self.send(pkt.into())?;
|
||||
|
||||
let pkt = self.recv()?;
|
||||
if let VariablePacket::SubackPacket(_pkt) = pkt {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Expected SubAck, received {:?}", pkt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv_pkt(&mut self) -> Result<Option<Pkt>> {
|
||||
match self.recv() {
|
||||
Ok(pkt) => Ok(Some(Pkt::try_from(pkt)?)),
|
||||
// search for any io error and check if it's ErrorKind::WouldBlock
|
||||
Err(MqttRecvError::Recv(
|
||||
VariablePacketError::IoError(err)
|
||||
| VariablePacketError::FixedHeaderError(FixedHeaderError::IoError(err)),
|
||||
)) if err.kind() == io::ErrorKind::WouldBlock => Ok(None),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ping(&mut self) -> Result<()> {
|
||||
let pkt = PingreqPacket::new();
|
||||
let pkt = VariablePacket::PingreqPacket(pkt);
|
||||
pkt.encode(&mut self.stream)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Pkt {
|
||||
#[serde(rename = "publish")]
|
||||
Publish(Publish),
|
||||
#[serde(rename = "pong")]
|
||||
Pong,
|
||||
}
|
||||
|
||||
impl Pkt {
|
||||
pub fn to_lua(&self) -> Result<AnyLuaValue> {
|
||||
let v = serde_json::to_value(self)?;
|
||||
let v = LuaJsonValue::from(v).into();
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<VariablePacket> for Pkt {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(pkt: VariablePacket) -> Result<Pkt> {
|
||||
match pkt {
|
||||
VariablePacket::ConnectPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::ConnackPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PublishPacket(pkt) => Ok(Pkt::Publish(Publish {
|
||||
topic: pkt.topic_name().to_string(),
|
||||
body: pkt.payload().to_vec(),
|
||||
})),
|
||||
VariablePacket::PubackPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PubrecPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PubrelPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PubcompPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PingreqPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::PingrespPacket(_) => Ok(Pkt::Pong),
|
||||
VariablePacket::SubscribePacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::SubackPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::UnsubscribePacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::UnsubackPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
VariablePacket::DisconnectPacket(_) => bail!("Unsupported pkt: {:?}", pkt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Publish {
|
||||
pub topic: String,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrootable_https::dns::Resolver;
|
||||
|
||||
fn connect() -> Result<MqttClient> {
|
||||
let resolver = Resolver::from_system_v4().unwrap();
|
||||
let url = "mqtt://mqtt.winkekatze24.de".parse()?;
|
||||
MqttClient::connect(&resolver, url, &MqttOptions::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_connect() {
|
||||
connect().expect("Failed to setup connection");
|
||||
}
|
||||
|
||||
// this test is too flaky
|
||||
/*
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_subscribe() {
|
||||
let mut c = connect().unwrap();
|
||||
c.subscribe("#", 0).unwrap();
|
||||
}
|
||||
*/
|
||||
}
|
||||
270
sn0int-std/src/psl.rs
Normal file
270
sn0int-std/src/psl.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use crate::errors::*;
|
||||
use crate::lazy::LazyInit;
|
||||
use chrootable_https::Client;
|
||||
use publicsuffix::{List, Psl as _};
|
||||
use std::fs::{self, File};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct DnsName {
|
||||
pub fulldomain: Option<String>,
|
||||
pub root: String,
|
||||
pub suffix: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PslReader {
|
||||
Reader(File),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl PslReader {
|
||||
pub fn open_or_download<F>(cache_dir: &Path, indicator: F) -> Result<PslReader>
|
||||
where
|
||||
F: Fn(Box<dyn Fn() -> Result<PslReader>>) -> Result<PslReader>,
|
||||
{
|
||||
let path = Self::path(cache_dir)?;
|
||||
let reader = match Self::open_from(&path) {
|
||||
Ok(r) => r,
|
||||
Err(_) => indicator(Box::new(move || {
|
||||
PslReader::download(&path, publicsuffix::LIST_URL)?;
|
||||
Self::open_from(&path)
|
||||
}))?,
|
||||
};
|
||||
Ok(reader)
|
||||
}
|
||||
|
||||
pub fn open(cache_dir: &Path) -> Result<PslReader> {
|
||||
let path = Self::path(cache_dir)?;
|
||||
Self::open_from(&path)
|
||||
}
|
||||
|
||||
pub fn open_from(path: &Path) -> Result<PslReader> {
|
||||
let file = File::open(path)?;
|
||||
Ok(PslReader::Reader(file))
|
||||
}
|
||||
|
||||
pub fn path(cache_dir: &Path) -> Result<PathBuf> {
|
||||
// use system path if exists
|
||||
let path = Path::new("/usr/share/publicsuffix/public_suffix_list.dat");
|
||||
if path.exists() {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
// else, use local cache
|
||||
let path = cache_dir.join("public_suffix_list.dat");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn download(path: &Path, url: &str) -> Result<()> {
|
||||
let client = Client::with_system_resolver_v4()?;
|
||||
let resp = client
|
||||
.get(url)
|
||||
.wait_for_response()
|
||||
.context("http request failed")?;
|
||||
fs::write(path, &resp.body)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LazyInit<Arc<Psl>> for PslReader {
|
||||
fn initialize(self) -> Result<Arc<Psl>> {
|
||||
let list = match self {
|
||||
PslReader::Reader(mut file) => {
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
buf
|
||||
}
|
||||
PslReader::String(s) => s,
|
||||
};
|
||||
|
||||
let list = List::from_str(&list)
|
||||
.map_err(|e| format_err!("Failed to load public suffix list: {}", e))?;
|
||||
|
||||
Ok(Arc::new(Psl { list }))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Psl {
|
||||
list: List,
|
||||
}
|
||||
|
||||
impl Psl {
|
||||
pub fn parse_dns_name(&self, name: &str) -> Result<DnsName> {
|
||||
let bytes = name.as_bytes();
|
||||
|
||||
let suffix = self
|
||||
.list
|
||||
.suffix(bytes)
|
||||
.ok_or_else(|| format_err!("Failed to detect suffix"))?;
|
||||
let suffix = String::from_utf8(suffix.as_bytes().to_vec())?;
|
||||
|
||||
let root = if let Some(root) = self.list.domain(bytes) {
|
||||
String::from_utf8(root.as_bytes().to_vec())?
|
||||
} else {
|
||||
// this is technically a tld, but support eg. a.prod.fastly.net anyway
|
||||
name.to_string()
|
||||
};
|
||||
|
||||
let fulldomain = if root == name {
|
||||
None
|
||||
} else {
|
||||
Some(name.to_string())
|
||||
};
|
||||
|
||||
Ok(DnsName {
|
||||
fulldomain,
|
||||
root,
|
||||
suffix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn init() -> Arc<Psl> {
|
||||
PslReader::String(
|
||||
r#"
|
||||
// ===BEGIN ICANN DOMAINS===
|
||||
com
|
||||
// ===END ICANN DOMAINS===
|
||||
// ===BEGIN PRIVATE DOMAINS===
|
||||
a.prod.fastly.net
|
||||
// ===END PRIVATE DOMAINS===
|
||||
"#
|
||||
.into(),
|
||||
)
|
||||
.initialize()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_example_com() {
|
||||
let x = init()
|
||||
.parse_dns_name("example.com")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: None,
|
||||
root: "example.com".into(),
|
||||
suffix: "com".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_www_example_com() {
|
||||
let x = init()
|
||||
.parse_dns_name("www.example.com")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: Some("www.example.com".into()),
|
||||
root: "example.com".into(),
|
||||
suffix: "com".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_com() {
|
||||
let x = init().parse_dns_name("com").expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: None,
|
||||
root: "com".into(),
|
||||
suffix: "com".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_a_b_c_d_e_f_g_com() {
|
||||
let x = init()
|
||||
.parse_dns_name("a.b.c.d.e.f.g.com")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: Some("a.b.c.d.e.f.g.com".into()),
|
||||
root: "g.com".into(),
|
||||
suffix: "com".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_empty() {
|
||||
let x = init().parse_dns_name("").is_err();
|
||||
assert!(x);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_asdfinvalid() {
|
||||
let x = init()
|
||||
.parse_dns_name("asdfinvalid")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: None,
|
||||
root: "asdfinvalid".into(),
|
||||
suffix: "asdfinvalid".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_www_example_asdfinvalid() {
|
||||
let x = init()
|
||||
.parse_dns_name("www.example.asdfinvalid")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: Some("www.example.asdfinvalid".into()),
|
||||
root: "example.asdfinvalid".into(),
|
||||
suffix: "asdfinvalid".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_a_prod_fastly_net() {
|
||||
let x = init()
|
||||
.parse_dns_name("a.prod.fastly.net")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: None,
|
||||
root: "a.prod.fastly.net".into(),
|
||||
suffix: "a.prod.fastly.net".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_psl_www_a_prod_fastly_net() {
|
||||
let x = init()
|
||||
.parse_dns_name("www.a.prod.fastly.net")
|
||||
.expect("parse_dns_name");
|
||||
assert_eq!(
|
||||
x,
|
||||
DnsName {
|
||||
fulldomain: None,
|
||||
root: "www.a.prod.fastly.net".into(),
|
||||
suffix: "a.prod.fastly.net".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user