250 Commits

Author SHA1 Message Date
kpcyrd
1be1967d6f Release v0.24.3 2022-11-13 03:22:10 +01:00
kpcyrd
5364328e2a Merge pull request #236 from kpcyrd/bump
Fix clone3 sandbox crash, improve sandbox crash error message
2022-11-13 02:19:54 +00:00
kpcyrd
01bcb10833 Fix chrono deprecation warnings 2022-11-13 03:01:07 +01:00
kpcyrd
56f7b1c646 Fix winkekatze-sub.lua example module 2022-11-12 21:47:15 +01:00
kpcyrd
dc26c14da4 Allow clone3 syscall in sandbox (#235) 2022-11-12 21:46:47 +01:00
kpcyrd
a2c70e43de Replace EOF while parsing a value at line 1 column 0 error with Sandbox child has crashed 2022-11-12 21:37:05 +01:00
kpcyrd
b28276c42e Update dependencies 2022-11-12 21:24:19 +01:00
kpcyrd
aa3311bfab Merge pull request #234 from SpriteOvO/riscv64
Support compiling for RISC-V 64-bit
2022-09-29 16:43:42 +00:00
Sprite
edb5b4bf25 Support compiling for RISC-V 64-bit 2022-09-28 21:18:02 +08:00
kpcyrd
231d3293fe Merge pull request #229 from kpcyrd/bump
Update dependencies
2022-07-17 17:02:38 +00:00
kpcyrd
febb39ab03 Update dependencies 2022-07-17 18:49:21 +02:00
kpcyrd
ebc1fa791d Fix clippy warnings, reduce heap allocations 2022-07-14 00:04:38 +02:00
kpcyrd
3ea88d9bd5 Merge pull request #227 from stoeckmann/paths
Fix error messages in paths.rs
2022-04-13 22:49:41 +00:00
Tobias Stoeckmann
006e3618fb Unify function bodies 2022-04-13 22:29:38 +02:00
Tobias Stoeckmann
c4b74aa6fc Adjust error messages to show target directory 2022-04-13 22:29:36 +02:00
kpcyrd
6cd97d980d Release v0.24.2 2022-03-29 02:38:09 +02:00
kpcyrd
b4f2591378 Merge pull request #225 from kpcyrd/bump
Update dependencies
2022-03-29 00:27:34 +00:00
kpcyrd
3a204a92a5 Add docker release github action 2022-03-29 01:16:52 +02:00
kpcyrd
b65c72d090 Update Dockerfile to alpine 3.15 and buildkit 2022-03-28 20:40:15 +02:00
kpcyrd
035ef9aa76 Add missing seccomp syscall (rseq) 2022-03-28 19:25:22 +02:00
kpcyrd
35c6501623 Update dependencies 2022-03-28 18:44:32 +02:00
kpcyrd
c0cb5840cc Merge pull request #224 from ysf/patch-1
Fixed typo
2022-03-21 17:57:30 +00:00
ysf
bba152d560 Fixed typo 2022-03-21 09:18:52 +01:00
kpcyrd
53a39d54bc Merge pull request #223 from definitepotato/issue-222
Adding syscall to seccomp sandbox.
2022-01-19 11:19:33 +00:00
definitepotato
455403baaf Adding syscall to seccomp sandbox. 2022-01-15 13:14:16 -05:00
kpcyrd
ec07344a0b Merge pull request #221 from kpcyrd/clippy
Fix clippy warnings
2022-01-14 15:56:14 +00:00
kpcyrd
c2d95659dc Fix clippy warnings 2022-01-13 20:05:34 +01:00
kpcyrd
cd99ac3911 Release v0.24.1 2021-12-05 16:17:51 +01:00
kpcyrd
f2a5dbc60c Merge pull request #219 from kpcyrd/seccomp
seccomp: Allow fstat call
2021-12-05 12:05:12 +00:00
kpcyrd
6425483c2b seccomp: Allow fstat call 2021-12-05 12:37:20 +01:00
kpcyrd
25f940788f Release v0.24.0 2021-12-05 01:15:41 +01:00
kpcyrd
c232b23b1e Merge pull request #217 from kpcyrd/rescope
Add rescope command to run autonoscope rules on existing db
2021-12-04 23:40:51 +00:00
kpcyrd
5b19c8c4b3 Add basic completions for interactive shell 2021-12-03 18:38:36 +01:00
kpcyrd
aeba3f4574 Allow statx and lseek syscalls 2021-12-03 18:38:36 +01:00
kpcyrd
1f5ea402ea Support rescoping with filters 2021-12-03 14:27:06 +01:00
kpcyrd
d54ffd1eba Add advanced interactive mode for rescope 2021-12-03 13:49:03 +01:00
kpcyrd
5bca5677b6 Add rescope command to run autonoscope rules on existing db 2021-12-03 04:11:53 +01:00
kpcyrd
af021cb6be Allow invoking sn0int autonoscope and sn0int autoscope from cli 2021-12-03 01:05:25 +01:00
kpcyrd
52891cf6b1 Fix compile warning about dead code 2021-12-03 00:52:29 +01:00
kpcyrd
6e76c035df Bump dependencies 2021-12-03 00:43:41 +01:00
kpcyrd
850d628a93 Release v0.23.0 2021-10-23 20:02:24 +02:00
kpcyrd
5bb03d39bc Merge pull request #215 from kpcyrd/user-agent
Allow setting a different default user agent
2021-10-23 12:23:24 +00:00
kpcyrd
f5660570d2 Update dependencies 2021-10-23 14:03:12 +02:00
kpcyrd
f1b0608daa Allow setting a different default user agent 2021-10-23 13:50:32 +02:00
kpcyrd
c775ae1dee Add patreon link 2021-10-23 13:15:50 +02:00
kpcyrd
36b5219008 Update readme 2021-10-02 15:03:25 +02:00
kpcyrd
79982fd5f0 Merge pull request #214 from kpcyrd/img-hash
Add perceptual image hashing
2021-09-22 01:58:21 +00:00
kpcyrd
5fe71feec3 Fix regression for sn0int run -f ./foo.lua 2021-09-22 01:21:03 +02:00
kpcyrd
b8d4eb0f51 Add functions for perceptual image hashing 2021-09-22 01:19:59 +02:00
kpcyrd
e6444db009 Merge pull request #213 from kpcyrd/cli-proxy
Allow setting a proxy with `run -X <proxy>`
2021-09-13 10:19:38 +00:00
kpcyrd
7dcb6b81dc Allow setting a proxy with run -X <proxy> 2021-09-13 11:51:32 +02:00
kpcyrd
4fb56d91c2 Allow using run <module> interactively 2021-09-13 11:29:13 +02:00
kpcyrd
8c486b7e6e Release v0.22.0 2021-09-08 18:44:54 +02:00
kpcyrd
e939a28469 Merge pull request #210 from kpcyrd/bump
Update dependencies
2021-09-08 12:12:28 +00:00
kpcyrd
2dec9dd28c Update dependencies 2021-09-08 13:50:57 +02:00
kpcyrd
06459098b1 Merge pull request #209 from kpcyrd/update
Update dependencies, fix clippy warnings
2021-09-06 15:07:53 +00:00
kpcyrd
fc0447725c Fix clippy warnings 2021-09-06 16:26:45 +02:00
kpcyrd
8312695e46 Update dependencies 2021-09-06 15:44:45 +02:00
kpcyrd
6c8e2df632 Update to nom7 2021-08-21 13:31:09 +02:00
kpcyrd
b1ee2d4ce7 Merge pull request #208 from kpcyrd/update
Update dependencies
2021-08-17 20:48:13 +00:00
kpcyrd
4cbb72e4c8 Update dependencies 2021-08-17 21:22:48 +02:00
kpcyrd
9e76935f55 Merge pull request #207 from kpcyrd/readline-exec
Support invoking shell commands with `!` from readline
2021-08-01 17:00:10 +00:00
kpcyrd
8b5ad635fa Support invoking shell commands with ! from readline 2021-07-29 14:15:00 +02:00
kpcyrd
9f4914f419 Release v0.21.2 2021-06-23 23:12:19 +02:00
kpcyrd
f24b1b2b9b Update dockerfile baseimage 2021-06-23 23:07:37 +02:00
kpcyrd
735e2dbaf8 Merge pull request #205 from kpcyrd/update
Update dependencies, fix seccomp issue on x86_64 musl
2021-06-23 21:03:19 +00:00
kpcyrd
5409ed8cdb Fix integration tests 2021-06-23 17:33:09 +02:00
kpcyrd
d0a36be95a Update dependencies 2021-06-23 17:00:11 +02:00
kpcyrd
0ab8aace70 Fix seccomp segfault with open on x86_64 musl 2021-06-23 17:00:11 +02:00
kpcyrd
699ebad23a registry: Set user agent when authenticating with github 2021-06-23 16:22:31 +02:00
kpcyrd
84e0e62753 Release v0.21.1 2021-05-18 00:49:10 +02:00
kpcyrd
dc8aac1b0f Merge pull request #202 from kpcyrd/aarch64
Update dependencies to fix aarch64 on musl
2021-05-17 22:46:55 +00:00
kpcyrd
7b4d599951 Update dependencies to fix aarch64 on musl 2021-05-18 00:30:39 +02:00
kpcyrd
d88c722a4b Release v0.21.0 2021-05-12 19:19:30 +02:00
kpcyrd
d70fc8f4c0 Merge pull request #200 from kpcyrd/update
Update dependencies
2021-05-12 16:54:46 +00:00
kpcyrd
da08f3a4bb Update dependencies 2021-05-12 18:27:44 +02:00
kpcyrd
892fa0d3e4 Merge pull request #199 from kpcyrd/stats
Improve stats command
2021-04-07 01:03:21 +00:00
kpcyrd
605d691b28 Add select --values as shorthand for jq -r .value 2021-04-07 02:34:41 +02:00
kpcyrd
efb458a725 Allow deleting multiple workspaces at once 2021-04-07 02:25:58 +02:00
kpcyrd
84e147f709 Add stats -q for more compact output 2021-04-07 02:11:29 +02:00
kpcyrd
1876e9ac8d Add stats --all to show stats for all workspaces 2021-04-07 01:51:57 +02:00
kpcyrd
8525883b91 README: Add packaging status 2021-03-23 01:29:53 +00:00
kpcyrd
02c537c253 Merge pull request #197 from kpcyrd/stats
Add sn0int stats
2021-03-20 13:38:12 +00:00
kpcyrd
d84038dcc3 Add irc notifications for github actions 2021-03-18 03:31:07 +01:00
kpcyrd
becb44a5d7 Update dependencies 2021-03-18 02:38:47 +01:00
kpcyrd
6e1904eaf9 Extend interactive completions for pkg list 2021-03-14 19:38:44 +01:00
kpcyrd
d493857011 Add stats --json output 2021-03-14 18:40:47 +01:00
kpcyrd
3af9abde1b Add stats command 2021-03-14 01:34:42 +01:00
kpcyrd
0ea8c46578 Release v0.20.1 2021-03-07 16:07:34 +01:00
kpcyrd
2e231da60c Merge pull request #196 from kpcyrd/update
Speedup initial database setup
2021-03-07 14:58:12 +00:00
kpcyrd
a97a7c6a4c Speedup docker build on github actions 2021-03-07 15:40:02 +01:00
kpcyrd
2b09ca997c Introduce github actions 2021-03-07 14:44:09 +01:00
kpcyrd
66fa77d16e Speedup initial database setup 2021-03-07 14:11:37 +01:00
kpcyrd
23e06ede3b Update dependencies 2021-03-07 14:11:25 +01:00
kpcyrd
7b994d7cae Merge pull request #195 from steev/master
Mention pkg-config being needed.
2021-01-03 20:06:05 +00:00
Steev Klimaszewski
59b591d0e8 Mention pkg-config being needed.
Not all installations have build tools installed, and libsodium requires
calling out to pkg-config so add it to the build instructions on Debian
so that the build doesn't fail.
2021-01-03 10:54:13 -06:00
kpcyrd
309be4b22f Release v0.20.0 2020-12-28 20:47:45 +01:00
kpcyrd
02d0ccce64 Merge pull request #194 from kpcyrd/bump
Bump dependencies
2020-12-28 19:03:23 +00:00
kpcyrd
a15d8167b9 Fix clippy warnings 2020-12-28 18:12:45 +01:00
kpcyrd
d125d91f0d Update dependencies 2020-12-28 17:13:40 +01:00
kpcyrd
01e4f87420 Update dependencies 2020-12-28 15:58:10 +01:00
kpcyrd
037189ec57 Merge pull request #189 from kpcyrd/redirects
Add option to follow redirects
2020-12-28 13:17:22 +00:00
kpcyrd
0d77c06a95 Fix default user agent 2020-11-01 23:20:28 +01:00
kpcyrd
247648aff8 Add redirect support 2020-11-01 23:04:06 +01:00
kpcyrd
f73c375d12 Merge pull request #187 from kpcyrd/add-stdin
Allow streaming some structs from stdin into db
2020-11-01 20:11:59 +00:00
kpcyrd
484507e033 Merge pull request #188 from kpcyrd/inverse-rules
Add inverse rules
2020-11-01 20:04:42 +00:00
kpcyrd
27588f5319 Allow streaming some structs from stdin into db 2020-11-01 19:28:50 +01:00
kpcyrd
c2b535eeed Merge pull request #186 from kpcyrd/bump
Bump dependencies
2020-11-01 16:33:44 +00:00
kpcyrd
410a381643 Update kali install instructions 2020-11-01 03:46:56 +01:00
kpcyrd
f702f48708 Update dependencies 2020-10-27 00:56:26 +01:00
kpcyrd
031a269de0 Update cert parser 2020-10-27 00:49:34 +01:00
kpcyrd
c1b38c8fa6 Add inverse rules 2020-10-27 00:07:00 +01:00
kpcyrd
afe302c101 Update dependencies 2020-10-02 21:23:28 +02:00
kpcyrd
b09c820a11 Merge pull request #183 from kpcyrd/stealth
Add support to track module stealth in metadata
2020-09-25 21:20:32 +00:00
kpcyrd
301ad3cf44 Show possible stealth values in help 2020-09-25 22:51:03 +02:00
kpcyrd
53b1e9fd1a Align pkg-search style with pkg-list 2020-09-25 22:41:33 +02:00
kpcyrd
5c447d259f Update dependencies 2020-09-25 20:50:14 +02:00
kpcyrd
67082a1002 Allow filtering by stealth level in pkg list 2020-09-13 22:40:42 +02:00
kpcyrd
152dd82848 Show stealth status in pkg list 2020-09-13 22:19:01 +02:00
kpcyrd
21c70aaf58 Add author and repository meta fields 2020-09-13 21:36:33 +02:00
kpcyrd
a93d9ddbe2 Add -- Stealth metadata option 2020-09-13 21:24:17 +02:00
kpcyrd
1de7f1a2d2 Highlight weekends in cal time/hour view 2020-07-08 02:21:41 +02:00
kpcyrd
4a82171ca1 Correctly clear terminal after worker::spawn_multi 2020-06-26 03:18:11 +02:00
kpcyrd
1828b67509 Release v0.19.1 2020-06-24 00:46:45 +02:00
kpcyrd
8f2d71e631 Merge pull request #181 from kpcyrd/notify-fix
Fix early exit on notification and make ratelimiter more global
2020-06-23 23:11:59 +02:00
kpcyrd
2277089bda Share the ratelimit state between main modules and all notifications 2020-06-23 05:41:24 +02:00
kpcyrd
63f226f68f Prevent notifications from interfering with ctrl-c register 2020-06-23 04:55:16 +02:00
kpcyrd
da0e6aa482 Release v0.19.0 2020-06-18 21:06:11 +02:00
kpcyrd
82a2bb1bd0 Merge pull request #180 from kpcyrd/exit
Make sn0int more forgiving with accidential ^C
2020-06-18 18:46:29 +02:00
kpcyrd
b96eea97ee Add exit and quit to completion, remove quickstart 2020-06-18 03:38:16 +02:00
kpcyrd
fd420f4107 Make sn0int more forgiving with accidential ^C 2020-06-18 03:38:16 +02:00
kpcyrd
c5a23a5929 Merge pull request #179 from kpcyrd/notify
Trigger events on database insert/update/delete
2020-06-13 16:06:22 +02:00
kpcyrd
eb2e6203be Trigger notification on ttl expire 2020-06-13 00:48:11 +02:00
kpcyrd
b838dc5b31 Refactor db add/update logging code 2020-06-12 23:24:24 +02:00
kpcyrd
983df4a12e Trigger events on database inserts and updates 2020-06-12 21:32:15 +02:00
kpcyrd
9825bad1fe Merge pull request #178 from kpcyrd/notify
Add basic notification system
2020-06-12 04:13:09 +02:00
kpcyrd
0183e5dbcf Document signal notifications 2020-06-12 03:21:46 +02:00
kpcyrd
dacc28e2f1 Document discord notifications 2020-06-12 00:02:22 +02:00
kpcyrd
90b3abc471 Allow access to debug logs in notify scripts 2020-06-11 23:57:53 +02:00
kpcyrd
09333ebd0d Fix timer description 2020-06-11 04:10:11 +02:00
kpcyrd
e8b1b1ac87 Document notifications with pushover 2020-06-11 03:43:51 +02:00
kpcyrd
eb56a66b20 Include example code to write scripts with options 2020-06-11 03:23:37 +02:00
kpcyrd
fbd9909fef Document new notification system 2020-06-11 03:15:24 +02:00
kpcyrd
51e76b4c89 Connect db_activity to notification system 2020-06-11 01:51:59 +02:00
kpcyrd
484a426834 Fix workspace filter 2020-06-10 03:25:40 +02:00
kpcyrd
31b8840f8c Implement glob patterns for topics 2020-06-10 03:11:25 +02:00
kpcyrd
dfc13be9cc Add notify system 2020-06-10 01:53:43 +02:00
kpcyrd
d6bb6a9a1f Merge pull request #176 from kpcyrd/seccomp
Fix seccomp issues on arm
2020-06-09 13:51:39 +02:00
kpcyrd
bfbe8f2c1a Merge pull request #177 from kpcyrd/cal
Implement calendar function
2020-06-09 13:51:31 +02:00
kpcyrd
289b0edbb3 Fix seccomp issues on arm 2020-06-09 01:26:33 +02:00
kpcyrd
b69e5f879b Merge pull request #175 from kpcyrd/bump-deps
Bump dependencies
2020-06-08 14:40:12 +02:00
kpcyrd
d8810a449c Implement an hourly-view 2020-06-08 03:24:26 +02:00
kpcyrd
bdcd052500 Add time view with 12min slices 2020-06-08 03:24:26 +02:00
kpcyrd
26aa17bf4d Fix crash with low activity 2020-06-08 03:24:26 +02:00
kpcyrd
7a6d6cf2d5 Integrate activity into calendar 2020-06-08 03:24:26 +02:00
kpcyrd
e9cdc40821 Implement activity annotation with dummy data 2020-06-08 03:24:26 +02:00
kpcyrd
c4c7b420ce Implement calendar function 2020-06-08 03:24:26 +02:00
kpcyrd
3d304f13bc Bump dependencies 2020-06-08 03:21:22 +02:00
kpcyrd
6ee61c189e Merge pull request #174 from kpcyrd/cargo-deb
Add cargo-deb metadata
2020-06-08 03:19:17 +02:00
kpcyrd
5ad5666caf Merge pull request #173 from kpcyrd/docs
Update install instructions
2020-06-08 01:46:47 +02:00
kpcyrd
e057f8e6be Merge pull request #172 from kpcyrd/seccomp
Fix seccomp issue: clock_nanosleep
2020-06-08 01:45:31 +02:00
kpcyrd
baf44b4882 Add cargo-deb metadata 2020-06-08 01:43:51 +02:00
kpcyrd
f7fda8de4c Fix typo in sandbox docs 2020-06-07 23:59:39 +02:00
kpcyrd
43154cf841 Update install instructions 2020-06-07 23:59:39 +02:00
kpcyrd
49f082875d Update syscallz dependency 2020-06-07 23:13:48 +02:00
kpcyrd
1e575e0dbe Fix seccomp issue: clock_nanosleep 2020-06-07 23:13:48 +02:00
kpcyrd
d741020ff8 Merge pull request #168 from 0x646e78/fedora-install-notes
Add install details for Fedora/RH based systems
2020-06-06 14:33:11 +02:00
DNX
33dca47b38 Add install details for Fedora/RH based systems 2020-05-25 10:20:49 +10:00
kpcyrd
33bd4f9e94 Merge pull request #167 from kpcyrd/agent
Improve `pkg quickstart`, deprecate `mod`
2020-05-09 03:07:35 +02:00
kpcyrd
7a75a7a255 Prevent insertion of domains/subdomains with asterisks 2020-05-05 14:09:34 +02:00
kpcyrd
1c23995c86 Bump dependencies 2020-04-19 15:59:37 +02:00
kpcyrd
23ae7491e3 Make pkg quickstart skip already installed modules 2020-04-01 00:30:07 +02:00
kpcyrd
487578f974 Add deprecation notice for mod command 2020-03-31 23:44:03 +02:00
kpcyrd
4dec06843f Include os version into api useragent 2020-03-24 02:26:52 +01:00
kpcyrd
5f31289430 Release v0.18.2 2020-03-23 19:03:45 +01:00
kpcyrd
b519619324 Fix incomplete osx 10.13 dns bugfix 2020-03-23 19:01:15 +01:00
kpcyrd
5ff78297e4 Release v0.18.1 2020-03-23 15:52:32 +01:00
kpcyrd
c36e0e9a60 Bump dependencies 2020-03-23 15:40:57 +01:00
kpcyrd
6e5a41fa34 Merge pull request #164 from kpcyrd/count
Add select --count
2020-03-23 15:39:58 +01:00
kpcyrd
896e13373e Work around issue with ipv6 dns resolvers 2020-03-21 18:23:53 +01:00
kpcyrd
baeb200a1c Improve error messages 2020-03-20 21:52:09 +01:00
kpcyrd
6648a35f17 Add select --count 2020-03-20 21:52:09 +01:00
kpcyrd
62204e2f31 Merge pull request #162 from kpcyrd/travis
Fix misc issues
2020-03-16 01:50:00 +01:00
kpcyrd
98c1272874 Merge pull request #161 from kpcyrd/oauth
Use new github oauth endpoint
2020-03-16 01:18:13 +01:00
kpcyrd
27df923256 Use new github oauth endpoint 2020-03-16 00:13:35 +01:00
kpcyrd
872d279c49 Support patterns in pkg list 2020-03-15 15:39:41 +01:00
kpcyrd
b548446759 Fix display of netblocks in detailed view 2020-03-15 15:36:18 +01:00
kpcyrd
d5ec02b3d7 Include target triple in sn0int registry requests 2020-03-08 16:25:32 +01:00
kpcyrd
b60de4547f Update travis irc notifications 2020-03-08 15:13:44 +01:00
kpcyrd
7ab4cbd745 Release v0.18.0 2020-03-07 17:30:36 +01:00
kpcyrd
51fa7a02ae Merge pull request #160 from kpcyrd/url-none-bytes
Properly support inserting urls with no body
2020-03-07 16:26:21 +01:00
kpcyrd
39bcc40f55 Document new crypto functions 2020-03-07 15:34:19 +01:00
kpcyrd
38c16d62ee Document mqtt functions 2020-03-07 15:19:02 +01:00
kpcyrd
ecd843c74f docs: extract autonoscope section 2020-03-07 14:59:05 +01:00
kpcyrd
5611d54131 Switch docker container to alpine 2020-03-07 14:49:00 +01:00
kpcyrd
bd5aaaedcd Properly support inserting urls with no body 2020-03-07 03:40:16 +01:00
kpcyrd
c50b770b1e Merge pull request #159 from kpcyrd/mqtt
Add mqtt and libsodium functions
2020-03-05 19:50:45 +01:00
kpcyrd
80f794b521 Fully disable flaky mqtt test 2020-03-05 16:56:17 +01:00
kpcyrd
e22c346537 Allow more direct access to mqtt pkts 2020-03-05 15:51:20 +01:00
kpcyrd
eed2da40b4 Fix flaky test 2020-03-05 10:51:20 +01:00
kpcyrd
b5ba669d10 Do not error for read timeouts in sock_recvline 2020-03-05 03:06:27 +01:00
kpcyrd
832a2608f4 Support geoipupdate path 2020-03-05 03:03:30 +01:00
kpcyrd
ef4b3226ea Add libsodium on osx 2020-03-05 02:29:16 +01:00
kpcyrd
54f2e60695 Add binary support to http_request/http_send 2020-03-05 01:58:45 +01:00
kpcyrd
6f1125516c Add libsodium support for decryption 2020-03-03 16:34:06 +01:00
kpcyrd
76042da044 Add mqtt functions 2020-03-03 01:35:16 +01:00
kpcyrd
193d855f69 Suggest a smaller number of concurrency 2020-02-29 01:39:00 +01:00
kpcyrd
65f282ac4c Update install instructions on sn0int.com 2020-02-29 01:38:24 +01:00
kpcyrd
5fc97140f3 Point to online docs on first start 2020-02-29 01:36:38 +01:00
kpcyrd
0ce8b70f09 Replace quickstart with pkg quickstart 2020-02-29 01:31:01 +01:00
kpcyrd
b4fbca4e0d Merge pull request #158 from kpcyrd/update
Update dependencies
2020-02-28 17:39:05 +01:00
kpcyrd
621bd9304c Document strval and intval 2020-02-28 16:48:00 +01:00
kpcyrd
1ab5972f90 Add more advanced time references 2020-02-28 16:45:36 +01:00
kpcyrd
8aaa5bd167 Update pledge 2020-02-28 15:35:11 +01:00
kpcyrd
4c89888a67 Change update check interval 2020-02-28 15:32:44 +01:00
kpcyrd
fc4d076113 Update x509-parser 2020-02-23 23:37:40 +01:00
kpcyrd
798bd56e75 Release v0.17.1 2020-02-22 19:38:23 +01:00
kpcyrd
5359d1cb95 Merge pull request #157 from kpcyrd/sandbox
seccomp: whitelist ppoll instead of poll on aarch64
2020-02-22 19:34:17 +01:00
kpcyrd
ff7fe3936b Refactor ipc parent/child code 2020-02-22 18:28:33 +01:00
kpcyrd
d1d221f81e Improve child process logging 2020-02-22 17:46:36 +01:00
kpcyrd
e2acdf53a4 Downgrade from broken x509-parser release
https://github.com/rusticata/x509-parser/pull/27
2020-02-22 17:12:09 +01:00
kpcyrd
749d7efab5 Add sn0int run --dump-sandbox-init-msg for debugging 2020-02-22 16:52:36 +01:00
kpcyrd
d96a967fa9 Fix integration test 2020-02-22 16:04:35 +01:00
kpcyrd
3dddd0b041 Add exit and quit commands 2020-02-22 15:19:46 +01:00
kpcyrd
33f02bb832 seccomp: whitelist ppoll instead of poll on aarch64 2020-02-22 15:17:17 +01:00
kpcyrd
28a73e9399 Release v0.17.0 2020-02-20 23:59:30 +01:00
kpcyrd
040db1ecfe Merge pull request #155 from kpcyrd/str
Add two string functions, fix seccomp issue
2020-02-20 23:31:53 +01:00
kpcyrd
9212f5dbdd Add strval 2020-02-20 21:35:46 +01:00
kpcyrd
32c430c768 Merge pull request #156 from kpcyrd/db-activity
Return true if uniq activity event is already known
2020-02-20 21:05:24 +01:00
kpcyrd
f3d72cf482 Add missing documentation for db functions 2020-02-20 13:32:19 +01:00
kpcyrd
978df56ec4 Return true if uniq activity event is already known 2020-02-20 00:11:04 +01:00
kpcyrd
699695e20f Add additional labels to sn0int select output 2020-02-19 18:27:11 +01:00
kpcyrd
948a4a59cb Add subcommand to show sn0int filesystem paths 2020-02-16 20:49:45 +01:00
kpcyrd
dbb594d5da Add str_find and str_replace 2020-02-16 02:42:41 +01:00
kpcyrd
4f8a5da351 Fix seccomp: poll 2020-02-16 02:42:41 +01:00
kpcyrd
4d0d4fc992 Merge pull request #154 from kpcyrd/split
Split codebase into sn0int and sn0int-std
2020-02-16 02:42:03 +01:00
kpcyrd
31aa5cb6c0 travis: re-enable registry build 2020-02-16 01:10:11 +01:00
kpcyrd
a4c7894b9a Move sn0int-common location 2020-02-16 00:33:25 +01:00
kpcyrd
51fcffe1c3 Update dependencies 2020-02-16 00:33:25 +01:00
kpcyrd
1fc3b8aa86 Split into sn0int and sn0int-std 2020-02-16 00:33:25 +01:00
kpcyrd
c93f19c02a Merge pull request #153 from kpcyrd/geo
Add geo_polygon_contains
2020-02-04 06:48:23 +01:00
kpcyrd
2f7fbe199f Fix completions for pkg 2020-02-04 05:26:32 +01:00
kpcyrd
5f3361828b Add geo_polygon_contains 2020-02-03 20:19:14 +01:00
kpcyrd
910dd6f7c6 Rename Engine to Library 2020-02-03 20:19:14 +01:00
kpcyrd
f7819d87c0 Merge pull request #152 from kpcyrd/workspaces
Migrate to new workspace format
2020-02-03 20:18:16 +01:00
kpcyrd
52ce2f9d6e Migrate to new workspace format 2020-02-01 21:32:21 +01:00
kpcyrd
91c73f88f6 Merge pull request #151 from kpcyrd/pkg
sn0int pkg
2020-02-01 20:38:44 +01:00
kpcyrd
419293ec00 Do not init a shell for sn0int workspace 2020-02-01 18:21:28 +01:00
kpcyrd
361ea8a5d3 Add sn0int pkg subcommand 2020-02-01 17:40:30 +01:00
kpcyrd
e0074faaad Add new pkg command to replace mod 2020-02-01 17:18:44 +01:00
197 changed files with 11027 additions and 4768 deletions

View File

@@ -1,6 +1,7 @@
target
Dockerfile
.dockerignore
docker-compose.yml
docker
docs
ci

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: [kpcyrd]
patreon: kpcyrd

58
.github/workflows/docker-release.yml vendored Normal file
View 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
View 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
View 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 }})'

View File

@@ -1,59 +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
# https://github.com/rust-lang/rust/issues/68264
#- 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

5292
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "sn0int"
version = "0.16.0"
version = "0.24.3"
description = "Semi-automatic OSINT framework and package manager"
authors = ["kpcyrd <git@rxv.cc>"]
license = "GPL-3.0"
@@ -9,99 +9,97 @@ categories = ["command-line-utilities"]
readme = "README.md"
edition = "2018"
[badges]
travis-ci = { repository = "kpcyrd/sn0int" }
[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.13.0", path="sn0int-common" }
sn0int-std = { version="=0.24.3", path="sn0int-std" }
rustyline = "10.0"
log = "0.4"
env_logger = "0.7"
env_logger = "0.9"
hlua-badtouch = "0.4"
structopt = "0.3"
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.14"
rustls = { version="0.16", features=["dangerous_configuration"] }
webpki = "0.21"
webpki-roots = "0.18"
ct-logs = "0.6"
pem = "0.7"
base64 = "0.11"
chrootable-https = "0.16"
base64 = "0.13"
data-encoding = "2.1.2"
kuchiki = "0.7.2"
serde_urlencoded = "0.6"
serde = "1.0"
serde_derive = "1.0"
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.5"
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"
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.17"
strum_macros = "0.17"
tungstenite = { version = "0.9", default-features = false }
ipnetwork = "0.18"
strum = "0.24"
strum_macros = "0.24"
embedded-triple = "0.1.0"
humansize = "1.1.0"
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.4"
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.12"
nix = "0.16"
syscallz = "0.16"
nix = "0.24"
[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"

View File

@@ -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.15
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.15
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

View File

@@ -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

101
README.md
View File

@@ -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,39 @@ Mac OSX
brew install sn0int
Debian/Ubuntu/Kali
There are prebuilt packages signed by a debian maintainer. We can import the
key for this repository out of the debian keyring.
apt install debian-keyring
gpg -a --export --keyring /usr/share/keyrings/debian-maintainers.gpg git@rxv.cc | apt-key add -
apt-key adv --keyserver keyserver.ubuntu.com --refresh-keys git@rxv.cc
echo deb http://apt.vulns.sexy stable main > /etc/apt/sources.list.d/apt-vulns-sexy.list
apt update
apt install sn0int
Docker
docker run --rm --init -it -v "$PWD/.cache:/cache" -v "$PWD/.data:/data" 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 +101,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 &gt;= bullseye, Ubuntu &gt;= 20.04, Kali](https://sn0int.readthedocs.io/en/latest/install.html#debian-bullseye-ubuntu-20-04-kali)
- [Debian &lt;= buster, Ubuntu &lt;= 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 +126,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 +137,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)
@@ -127,6 +166,18 @@ For everything else please have a look at the [detailed list][1].
- [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)
@@ -154,6 +205,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)
@@ -175,14 +227,23 @@ 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)
@@ -215,11 +276,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)

View File

@@ -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',

View File

@@ -1,37 +0,0 @@
#!/bin/sh
set -exu
case "$1" in
build)
cargo build --verbose
cargo build --verbose --examples
;;
test)
ci/run.sh build
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

View File

@@ -1,8 +0,0 @@
#!/bin/sh
set -exu
case "$1" in
linux)
sudo apt update
sudo apt install libsqlite3-dev libseccomp-dev
;;
esac

View File

@@ -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 \

View File

@@ -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"]

View File

@@ -68,7 +68,7 @@ Logging an event that has a location attached could look like this:
topic='harness/activity-ping:dummy',
time=sn0int_time(),
latitude=40.726662,
longitude=-74.036677
longitude=-74.036677,
radius=50,
content={
a='b',

81
docs/autonoscope.rst Normal file
View 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

View File

@@ -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``.

View File

@@ -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
---------

View File

@@ -38,10 +38,12 @@ Getting Started
install
build
usage
autonoscope
scripting
database
structs
activity
notifications
keyring
config
sandbox

View File

@@ -19,12 +19,37 @@ Mac OSX
$ brew install sn0int
Debian/Ubuntu/Kali
------------------
Debian >= bullseye, Ubuntu >= 20.04, 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. We can import the
key for this repository out of the debian keyring.
.. code-block:: bash
$ sudo apt install debian-keyring
$ gpg -a --export --keyring /usr/share/keyrings/debian-maintainers.gpg kpcyrd@archlinux.org | sudo tee /etc/apt/trusted.gpg.d/apt-vulns-sexy.gpg
$ echo deb http://apt.vulns.sexy stable main | sudo tee /etc/apt/sources.list.d/apt-vulns-sexy.list
$ sudo apt update
$ sudo apt install sn0int
Debian <= buster, Ubuntu <= 19.10
---------------------------------
There are prebuilt packages signed by a debian maintainer. We can import the
key for this repository out of the debian keyring.
.. code-block:: bash
$ sudo apt install debian-keyring
$ gpg -a --export --keyring /usr/share/keyrings/debian-maintainers.gpg git@rxv.cc | sudo apt-key add -
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --refresh-keys git@rxv.cc
$ echo deb http://apt.vulns.sexy stable main | sudo tee /etc/apt/sources.list.d/apt-vulns-sexy.list
$ sudo apt update
$ sudo 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 +57,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 .
@@ -63,8 +88,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
-------
@@ -78,4 +110,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 .

311
docs/notifications.rst Normal file
View 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

View File

@@ -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
----------
@@ -990,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
------
@@ -1025,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
--------
@@ -1045,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
---------

View File

@@ -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

View File

@@ -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

View File

@@ -1,16 +1,8 @@
extern crate sn0int;
extern crate env_logger;
extern crate chrootable_https;
// 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 sn0int::errors::*;
use sn0int::geoip::{AsnDB, GeoIP, Maxmind};
use sn0int::paths;
use std::net::IpAddr;
use std::path::Path;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
@@ -27,8 +19,8 @@ pub struct AsnArgs {
}
impl AsnArgs {
fn run(&self) -> Result<()> {
let path = AsnDB::cache_path()?;
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)?;
@@ -44,8 +36,8 @@ pub struct GeoIPArgs {
}
impl GeoIPArgs {
fn run(&self) -> Result<()> {
let path = GeoIP::cache_path()?;
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)?;
@@ -59,9 +51,10 @@ impl GeoIPArgs {
fn run() -> Result<()> {
let args = Args::from_args();
debug!("{:?}", args);
let cache_dir = paths::cache_dir()?;
match args {
Args::Asn(args) => args.run(),
Args::GeoIP(args) => args.run(),
Args::Asn(args) => args.run(&cache_dir),
Args::GeoIP(args) => args.run(&cache_dir),
}
}

View 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;

View 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;

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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()

View 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

View 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

View 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

View File

@@ -1,5 +1,6 @@
-- Description: Send a request to a hidden service
-- Version: 0.1.0
-- Stealth: passive
-- License: GPL-3.0
function run()

View 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

View File

@@ -1,5 +1,6 @@
-- Description: basic selftest
-- Version: 0.1.0
-- Stealth: offline
-- License: GPL-3.0
function run()

View File

@@ -1,5 +1,6 @@
-- Description: Read from stdin
-- Version: 0.1.0
-- Stealth: offline
-- License: GPL-3.0
function run()

View 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

View File

@@ -0,0 +1,22 @@
-- 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
local pkt = mqtt_recv(sock)
if last_err() then return end
local log = {pkt=pkt}
if pkt then
log['text'] = utf8_decode(pkt['body'])
if last_err() then clear_err() end
end
info(log)
end
end

View File

@@ -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()

View File

@@ -1,16 +1,15 @@
[package]
name = "sn0int-common"
version = "0.9.0"
description = "Common code for sn0int"
version = "0.13.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"

View File

@@ -1,4 +1,5 @@
use crate::id::ModuleID;
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct WhoamiResponse {

View File

@@ -0,0 +1,2 @@
pub use anyhow::{Error, Context, anyhow, format_err, bail};
pub type Result<T> = ::std::result::Result<T, Error>;

View File

@@ -1,5 +1,4 @@
use crate::errors::*;
use nom;
use serde::{de, Serialize, Serializer, Deserialize, Deserializer};
use std::fmt;
use std::result;
@@ -53,7 +52,7 @@ impl FromStr for ModuleID {
fn from_str(s: &str) -> Result<ModuleID> {
let (trailing, module) = module(s)
.map_err(|err| format_err!("Failed to parse module id: {:?}", err))?;
.map_err(|err| anyhow!("Failed to parse module id: {:?}", err))?;
if !trailing.is_empty() {
bail!("Trailing data in module id");
}

View File

@@ -1,7 +1,3 @@
#[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::*;

View File

@@ -1,7 +1,13 @@
use crate::errors::*;
use nom::IResult;
use nom::bytes::complete::{tag, take_until};
use nom::combinator::map_res;
use nom::multi::fold_many0;
use serde::{Serialize, Deserialize};
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,
}
@@ -151,6 +169,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,6 +186,9 @@ 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>,
}
@@ -179,6 +203,14 @@ impl<'a> NewMetadata<'a> {
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()?;
@@ -187,28 +219,40 @@ 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| {
acc.push(item);
acc
}) >>
tag!("\n") >>
(lines)
));
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
}
)(input)?;
let (input, _) = tag("\n")(input)?;
Ok((input, lines))
}
#[cfg(test)]
mod tests {
@@ -227,6 +271,36 @@ mod tests {
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(),
});
}
@@ -243,6 +317,9 @@ mod tests {
version: "1.0.0".to_string(),
license: License::WTFPL,
source: None,
stealth: Stealth::Normal,
authors: vec![],
repository: None,
keyring_access: Vec::new(),
});
}

View File

@@ -0,0 +1,53 @@
use crate::errors::*;
use serde::{Serialize, Deserialize};
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq, PartialOrd, Clone, 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),
}
}
}

View File

@@ -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.13.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"

View File

@@ -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

View File

@@ -1,2 +0,0 @@
pub use failure::{Error, ResultExt};
pub type Result<T> = ::std::result::Result<T, Error>;

View File

@@ -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");

View File

@@ -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,

View File

@@ -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)
}
}

View File

@@ -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::*;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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")

View File

@@ -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;

View File

@@ -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 {

View File

@@ -1,3 +1,4 @@
use serde::Serialize;
use sn0int_registry::errors::*;
use sn0int_registry::db;
use sn0int_registry::models::*;

View File

@@ -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)

View File

@@ -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}}

56
sn0int-std/Cargo.toml Normal file
View File

@@ -0,0 +1,56 @@
[package]
name = "sn0int-std"
version = "0.24.3"
description = "sn0int - stdlib"
authors = ["kpcyrd <git@rxv.cc>"]
repository = "https://github.com/kpcyrd/sn0int"
license = "GPL-3.0"
edition = "2018"
# 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 = "1"
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.20"
bytes = "0.4"
base64 = "0.13"
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.4"
digest = "0.10"
blake2 = "0.10"
[dev-dependencies]
env_logger = "0.9"
maplit = "1.0.1"

119
sn0int-std/src/blobs.rs Normal file
View File

@@ -0,0 +1,119 @@
use bytes::Bytes;
use blake2::Blake2bVar;
use digest::{Update, VariableOutput};
use serde::ser::{Serialize, Serializer};
use serde::de::{self, Deserialize, Deserializer};
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)
.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");
}
}

View File

@@ -1,78 +1,11 @@
use crate::errors::*;
use x509_parser;
use der_parser::der::DerObject;
use der_parser::ber::{BerObjectContent, BerTag};
use der_parser::oid::Oid;
use serde::{Serialize, Deserialize};
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Debug, PartialEq)]
pub enum AlternativeName {
DnsName(String),
Email(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::Boolean, BerObjectContent::Unknown(BerTag::Boolean, value)) => san_value_email(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_email(v: &[u8]) -> Result<AlternativeName> {
debug!("Reading as email: {:?}", v);
String::from_utf8(v.to_vec())
.map(AlternativeName::Email)
.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::x509::X509Version;
use x509_parser::certificate::X509Certificate;
use x509_parser::extensions::{GeneralName, ParsedExtension};
use x509_parser::prelude::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Certificate {
@@ -83,7 +16,7 @@ pub struct Certificate {
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");
@@ -99,12 +32,12 @@ impl Certificate {
}
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
@@ -116,35 +49,45 @@ impl Certificate {
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::Email(v) => valid_emails.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);
}
},
_ => (),
}
}
}
}
@@ -162,7 +105,6 @@ impl Certificate {
#[cfg(test)]
mod tests {
use super::*;
use der_parser::parse_der;
#[test]
fn test_parse_pem_github() {
@@ -260,19 +202,6 @@ ZkZZmqNn2Q8=
});
}
#[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")),
]);
}
#[test]
fn test_long_san_extension() {
let mut x = Certificate::parse_pem(r#"-----BEGIN CERTIFICATE-----
@@ -372,48 +301,6 @@ z/6Vy8Ga9kigYVsa8ZFMR+Ex
});
}
#[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()));
}
#[test]
fn test_san_email() {
let mut x = Certificate::parse_pem(r#"-----BEGIN CERTIFICATE-----

51
sn0int-std/src/crypto.rs Normal file
View File

@@ -0,0 +1,51 @@
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]);
}
}

View File

@@ -0,0 +1 @@
pub mod structs;

View File

@@ -2,9 +2,6 @@ use crate::errors::*;
use crate::hlua::{AnyHashableLuaValue, AnyLuaValue, AnyLuaString};
use std::collections::{self, HashMap};
use crate::json::LuaJsonValue;
use serde;
use serde_json;
pub fn from_lua<T>(x: LuaJsonValue) -> Result<T>
where for<'de> T: serde::Deserialize<'de>
@@ -41,6 +38,12 @@ impl LuaMap {
pub fn insert_num<K: Into<String>>(&mut self, k: K, v: f64) {
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(())
}
}
impl IntoIterator for LuaMap {
@@ -83,16 +86,16 @@ 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)),
@@ -133,9 +136,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)
}
}
@@ -146,7 +149,7 @@ pub fn byte_array(bytes: AnyLuaValue) -> Result<Vec<u8>> {
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) =>
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)),

3
sn0int-std/src/errors.rs Normal file
View File

@@ -0,0 +1,3 @@
pub use log::{trace, debug, info, warn, error};
pub use failure::{Error, ResultExt, format_err, bail};
pub type Result<T> = ::std::result::Result<T, Error>;

110
sn0int-std/src/geo.rs Normal file
View File

@@ -0,0 +1,110 @@
use crate::errors::*;
use crate::hlua::AnyLuaValue;
use crate::json::LuaJsonValue;
use geo::{LineString, Polygon, Coordinate};
use geo::prelude::*;
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| Coordinate { 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);
}
}

View File

@@ -1,6 +1,5 @@
use crate::errors::*;
use crate::lazy::LazyInit;
use crate::paths;
use maxminddb::{self, geoip2};
use std::fmt;
use std::fs::{self, File};
@@ -19,13 +18,15 @@ pub trait Maxmind: Sized {
fn new(reader: maxminddb::Reader<Vec<u8>>) -> Self;
fn cache_path() -> Result<PathBuf> {
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::filename());
@@ -36,7 +37,7 @@ pub trait Maxmind: Sized {
}
// use cache path
let path = paths::cache_dir()?
let path = cache_dir
.join(Self::filename());
Ok(path)
}
@@ -52,8 +53,8 @@ pub trait Maxmind: Sized {
Self::from_buf(buf)
}
fn try_open_reader() -> Result<Option<MaxmindReader>> {
let path = Self::cache_path()?;
fn try_open_reader(cache_dir: &Path) -> Result<Option<MaxmindReader>> {
let path = Self::cache_path(cache_dir)?;
if path.exists() {
let db = MaxmindReader::open_path(path)?;

View File

@@ -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,14 @@ 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(),
})
}
}

View File

@@ -1,9 +1,7 @@
use crate::errors::*;
use exif;
use serde::{Serialize, Deserialize};
use std::io;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Location {
latitude: f64,
@@ -61,8 +59,9 @@ 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();
let location = Location::try_from_iter(fields).ok();

131
sn0int-std/src/gfx/mod.rs Normal file
View File

@@ -0,0 +1,131 @@
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());
}
}

View File

@@ -1,11 +1,8 @@
use crate::engine::structs::LuaMap;
use crate::errors::*;
use kuchiki;
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()
}
@@ -102,6 +99,7 @@ pub fn html_form(html: &str) -> Result<HashMap<String, String>> {
#[cfg(test)]
mod tests {
use super::*;
use maplit::hashmap;
#[test]
fn test_html_select() {

View File

@@ -1,6 +1,5 @@
use crate::errors::*;
use std::iter::FromIterator;
use std::collections::HashMap;
use crate::hlua::AnyLuaValue;
use serde_json::{self, Deserializer, Value, Number, Map};
@@ -22,7 +21,7 @@ pub fn encode(v: AnyLuaValue) -> Result<String> {
}
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 +34,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,9 +51,9 @@ 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
@@ -115,9 +110,9 @@ impl From<AnyLuaValue> for LuaJsonValue {
}
}
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),
@@ -126,9 +121,10 @@ impl Into<serde_json::Value> for LuaJsonValue {
.map(|x| x.into())
.collect()
),
LuaJsonValue::Object(v) => Value::Object(Map::from_iter(v.into_iter()
LuaJsonValue::Object(v) => Value::Object(v.into_iter()
.map(|(k, v)| (k, v.into()))
)),
.collect::<Map<_, _>>()
),
}
}
}
@@ -144,9 +140,10 @@ impl From<serde_json::Value> for LuaJsonValue {
.map(|x| x.into())
.collect()
),
Value::Object(v) => LuaJsonValue::Object(HashMap::from_iter(v.into_iter()
Value::Object(v) => LuaJsonValue::Object(v.into_iter()
.map(|(k, v)| (k, v.into()))
)),
.collect::<HashMap<_, _>>()
),
}
}
}

25
sn0int-std/src/lib.rs Normal file
View File

@@ -0,0 +1,25 @@
use hlua_badtouch as hlua;
pub mod blobs;
pub mod crt;
pub mod crypto;
mod errors;
pub mod engine;
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();
}

229
sn0int-std/src/mqtt.rs Normal file
View File

@@ -0,0 +1,229 @@
use chrootable_https::DnsResolver;
use crate::errors::*;
use crate::hlua::AnyLuaValue;
use mqtt::packet::VariablePacketError;
use crate::json::LuaJsonValue;
use crate::sockets::{Stream, SocketOptions};
use mqtt::{TopicFilter, QualityOfService};
use mqtt::control::ConnectReturnCode;
use mqtt::control::fixed_header::FixedHeaderError;
use mqtt::encodable::{Encodable, Decodable};
use mqtt::packet::{VariablePacket, ConnectPacket, SubscribePacket, PingreqPacket};
use serde::{Serialize, Deserialize};
use std::convert::TryFrom;
use std::io;
use std::net::SocketAddr;
use url::Url;
#[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,
#[serde(default)]
pub read_timeout: u64,
#[serde(default)]
pub write_timeout: u64,
}
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)
}
}
pub struct MqttClient {
stream: Stream,
}
impl MqttClient {
pub fn negotiate(stream: Stream, options: &MqttOptions) -> Result<MqttClient> {
let mut client = MqttClient {
stream,
};
let mut pkt = ConnectPacket::new("sn0int");
pkt.set_user_name(options.username.clone());
pkt.set_password(options.password.clone());
/*
if let Some(keep_alive) = msg.keep_alive {
packet.set_keep_alive(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,
};
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: options.read_timeout,
write_timeout: options.write_timeout,
})?;
Self::negotiate(stream, options)
}
fn send(&mut self, pkt: VariablePacket) -> Result<()> {
debug!("Sending mqtt packet: {:?}", pkt);
pkt.encode(&mut self.stream)?;
Ok(())
}
fn recv(&mut self) -> std::result::Result<VariablePacket, VariablePacketError> {
let pkt = VariablePacket::decode(&mut self.stream)?;
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)?)),
Err(VariablePacketError::IoError(err)) if err.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(VariablePacketError::FixedHeaderError(FixedHeaderError::IoError(err))) if err.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(err) => Err(Error::from(err))
}
}
pub fn ping(&mut self) -> Result<()> {
let pkt = PingreqPacket::new();
self.send(pkt.into())
}
}
#[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();
}
*/
}

View File

@@ -1,13 +1,12 @@
use crate::errors::*;
use chrootable_https::Client;
use crate::errors::*;
use crate::lazy::LazyInit;
use publicsuffix::{List, Psl as _};
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::paths;
use crate::lazy::LazyInit;
use crate::worker;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
pub struct DnsName {
@@ -23,22 +22,24 @@ pub enum PslReader {
}
impl PslReader {
pub fn open_or_download() -> Result<PslReader> {
let path = Self::path()?;
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(_) => worker::spawn_fn("Downloading public suffix list", || {
Err(_) => indicator(Box::new(move || {
PslReader::download(&path, publicsuffix::LIST_URL)?;
Self::open_from(&path)
}, false)?,
}))?,
};
Ok(reader)
}
pub fn open() -> Result<PslReader> {
let path = Self::path()?;
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> {
@@ -46,7 +47,7 @@ impl PslReader {
Ok(PslReader::Reader(file))
}
pub fn path() -> Result<PathBuf> {
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() {
@@ -54,13 +55,13 @@ impl PslReader {
}
// else, use local cache
let path = paths::cache_dir()?
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()?;
let client = Client::with_system_resolver_v4()?;
let resp = client.get(url)
.wait_for_response()
.context("http request failed")?;
@@ -72,11 +73,15 @@ impl PslReader {
impl LazyInit<Arc<Psl>> for PslReader {
fn initialize(self) -> Result<Arc<Psl>> {
let list = match self {
PslReader::Reader(file) => publicsuffix::List::from_reader(file),
PslReader::String(s) => publicsuffix::List::from_str(&s),
PslReader::Reader(mut file) => {
let mut buf = String::new();
file.read_to_string(&mut buf)?;
buf
},
PslReader::String(s) => s,
};
let list = list
let list = List::from_str(&list)
.map_err(|e| format_err!("Failed to load public suffix list: {}", e))?;
Ok(Arc::new(Psl {
@@ -87,32 +92,28 @@ impl LazyInit<Arc<Psl>> for PslReader {
#[derive(Debug)]
pub struct Psl {
list: publicsuffix::List,
list: List,
}
impl Psl {
pub fn parse_dns_name(&self, name: &str) -> Result<DnsName> {
let dns_name = self.list.parse_dns_name(name)
.map_err(|e| format_err!("Failed to parse dns_name: {}", e))?;
let bytes = name.as_bytes();
let (root, suffix) = if let Some(domain) = dns_name.domain() {
let root = domain.to_string();
if let Some(suffix) = domain.suffix() {
(root, suffix.to_string())
} else {
// XXX: not sure if this is reachable
(root.clone(), root)
}
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
// XXX: consider forcing domain to be in self.list.private()
(name.to_string(), name.to_string())
name.to_string()
};
let fulldomain = if root.as_str() != name {
Some(name.to_string())
} else {
let fulldomain = if root == name {
None
} else {
Some(name.to_string())
};
Ok(DnsName {

View File

@@ -1,12 +1,22 @@
use chrono::prelude::*;
use crate::worker::RatelimitSender;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use std::result;
use std::sync::mpsc;
use std::time::Duration;
pub type RatelimitSender = mpsc::Sender<result::Result<RatelimitResponse, String>>;
pub struct Ratelimiter {
buckets: HashMap<String, Bucket>,
}
impl Default for Ratelimiter {
fn default() -> Self {
Self::new()
}
}
impl Ratelimiter {
pub fn new() -> Ratelimiter {
Ratelimiter {

View File

@@ -6,6 +6,7 @@ use crate::json::LuaJsonValue;
use chrootable_https::dns::{DnsResolver, RecordType};
use chrootable_https::socks5::{self, ProxyDest};
use regex::Regex;
use serde::Deserialize;
use tokio::runtime::Runtime;
use std::fmt;
@@ -91,7 +92,7 @@ impl SocketOptions {
pub enum Stream {
Tcp(TcpStream),
Tls(rustls::StreamOwned<rustls::ClientSession, TcpStream>),
Tls(Box<rustls::StreamOwned<rustls::ClientSession, TcpStream>>),
}
impl Stream {
@@ -106,7 +107,7 @@ impl Stream {
let mut errors = Vec::new();
for addr in addrs {
match Stream::connect_addr(host, (addr, port).into(), &options) {
match Stream::connect_addr(host, (addr, port).into(), options) {
Ok(socket) => {
return Ok(socket);
},
@@ -232,7 +233,7 @@ impl Socket {
}
pub fn send(&mut self, data: &[u8]) -> Result<()> {
match str::from_utf8(&data) {
match str::from_utf8(data) {
Ok(data) => debug!("send: {:?}", data),
Err(_) => debug!("send: {:?}", data),
};
@@ -319,6 +320,7 @@ impl Socket {
let available = match self.stream.fill_buf() {
Ok(n) => n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(Vec::new()),
Err(e) => return Err(e.into())
};
@@ -363,7 +365,7 @@ mod tests {
#[ignore]
#[test]
fn verify_tls_good() {
let resolver = Resolver::from_system().unwrap();
let resolver = Resolver::from_system_v4().unwrap();
let _sock = Socket::connect(&resolver, "badssl.com", 443, &SocketOptions{
tls: true,
..Default::default()
@@ -373,7 +375,7 @@ mod tests {
#[test]
#[ignore]
fn verify_tls_good_request() {
let resolver = Resolver::from_system().unwrap();
let resolver = Resolver::from_system_v4().unwrap();
let mut sock = Socket::connect(&resolver, "badssl.com", 443, &SocketOptions{
tls: true,
..Default::default()
@@ -386,7 +388,7 @@ mod tests {
#[test]
#[ignore]
fn verify_tls_expired() {
let resolver = Resolver::from_system().unwrap();
let resolver = Resolver::from_system_v4().unwrap();
let sock = Socket::connect(&resolver, "expired.badssl.com", 443, &SocketOptions{
tls: true,
..Default::default()
@@ -400,7 +402,7 @@ mod tests {
#[test]
#[ignore]
fn verify_tls_1_1_1_1() {
let resolver = Resolver::from_system().unwrap();
let resolver = Resolver::from_system_v4().unwrap();
let _sock = Socket::connect(&resolver, "1.1.1.1", 443, &SocketOptions{
tls: true,
..Default::default()

View File

@@ -1,17 +1,14 @@
use crate::errors::*;
use crate::hlua::AnyLuaValue;
use crate::json::LuaJsonValue;
use rustls::{self, ClientConfig, Session, ClientSession};
use serde::Serialize;
use std::str;
use std::result;
use std::sync::Arc;
use std::net::TcpStream;
use super::{Stream, SocketOptions};
#[derive(Debug, Serialize)]
pub struct TlsData {
cert: Option<String>,
@@ -50,9 +47,9 @@ pub fn wrap(stream: TcpStream, host: &str, options: &SocketOptions) -> Result<(S
}
let dns_name = if let Some(v) = &options.sni_value {
get_dns_name(&mut config, &v)
get_dns_name(&mut config, v)
} else {
get_dns_name(&mut config, &host)
get_dns_name(&mut config, host)
};
let config = Arc::new(config);
@@ -61,7 +58,7 @@ pub fn wrap(stream: TcpStream, host: &str, options: &SocketOptions) -> Result<(S
}
fn get_dns_name(config: &mut ClientConfig, host: &str) -> webpki::DNSName {
if let Ok(name) = webpki::DNSNameRef::try_from_ascii_str(&host) {
if let Ok(name) = webpki::DNSNameRef::try_from_ascii_str(host) {
debug!("setting sni value to: {:?}", host);
name.to_owned()
} else {
@@ -107,7 +104,7 @@ fn setup(mut stream: TcpStream, mut session: ClientSession) -> Result<(Stream, T
info!("successfully established tls connection");
let stream = rustls::StreamOwned::new(session, stream);
let stream = Stream::Tls(stream);
let stream = Stream::Tls(Box::new(stream));
Ok((stream, tls))
}

View File

@@ -1,25 +1,25 @@
pub use chrootable_https::{Client, HttpClient, Resolver, Response};
use crate::blobs::Blob;
use std::collections::{HashMap, HashSet};
use std::time::Duration;
use std::ops::Deref;
use crate::blobs::{Blob, BlobState};
use crate::engine::structs::LuaMap;
use crate::errors::*;
use crate::json::LuaJsonValue;
use crate::hlua::AnyLuaValue;
use crate::engine::ctx::State;
use serde_json;
use chrootable_https::{Request, Body, Uri};
pub use chrootable_https::{Client, HttpClient, Resolver, Response};
use chrootable_https::http::HttpTryFrom;
use chrootable_https::http::uri::Parts;
use chrootable_https::http::request::Builder;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use serde::{Serialize, Deserialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::Write;
use std::iter;
use std::net::SocketAddr;
use serde::Serialize;
use crate::engine::structs::LuaMap;
use crate::json::LuaJsonValue;
use chrootable_https::http::uri::Parts;
use chrootable_https::{Request, Body, Uri};
use serde_urlencoded;
use base64;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use url::Url;
pub fn url_set_qs<S: Serialize + fmt::Debug>(url: Uri, query: &S) -> Result<Uri> {
let mut parts = Parts::from(url);
@@ -37,6 +37,12 @@ pub fn url_set_qs<S: Serialize + fmt::Debug>(url: Uri, query: &S) -> Result<Uri>
.map_err(Error::from)
}
pub trait WebState {
fn http(&self, proxy: &Option<SocketAddr>) -> Result<Arc<chrootable_https::Client<Resolver>>>;
fn register_in_jar(&self, session: &str, key: String, value: String);
}
#[derive(Debug)]
pub struct HttpSession {
id: String,
@@ -45,7 +51,12 @@ pub struct HttpSession {
impl HttpSession {
pub fn new() -> (String, HttpSession) {
let id: String = thread_rng().sample_iter(&Alphanumeric).take(16).collect();
let mut rng = thread_rng();
let id: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(16)
.collect();
(id.clone(), HttpSession {
id,
cookies: CookieJar::default(),
@@ -55,17 +66,21 @@ impl HttpSession {
#[derive(Debug, Default, Deserialize)]
pub struct RequestOptions {
query: Option<HashMap<String, String>>,
headers: Option<HashMap<String, String>>,
basic_auth: Option<(String, String)>,
user_agent: Option<String>,
json: Option<serde_json::Value>,
form: Option<serde_json::Value>,
body: Option<String>,
timeout: Option<u64>,
pub query: Option<HashMap<String, String>>,
pub headers: Option<HashMap<String, String>>,
pub basic_auth: Option<(String, String)>,
pub user_agent: Option<String>,
pub json: Option<serde_json::Value>,
pub form: Option<serde_json::Value>,
#[serde(default)]
into_blob: bool,
proxy: Option<SocketAddr>,
pub follow_redirects: usize,
pub body: Option<String>,
pub timeout: Option<u64>,
#[serde(default)]
pub into_blob: bool,
pub proxy: Option<SocketAddr>,
#[serde(default)]
pub binary: bool,
}
impl RequestOptions {
@@ -76,10 +91,6 @@ impl RequestOptions {
}
}
pub fn default_user_agent() -> String {
format!("sn0int/{}", env!("CARGO_PKG_VERSION"))
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct HttpRequest {
// reference to the HttpSession
@@ -90,18 +101,18 @@ pub struct HttpRequest {
query: Option<HashMap<String, String>>,
headers: Option<HashMap<String, String>>,
basic_auth: Option<(String, String)>,
user_agent: Option<String>,
user_agent: String,
follow_redirects: usize,
body: Option<ReqBody>,
timeout: Option<Duration>,
into_blob: bool,
proxy: Option<SocketAddr>,
binary: bool,
}
impl HttpRequest {
pub fn new(session: &HttpSession, method: String, url: String, options: RequestOptions) -> HttpRequest {
pub fn new(session: &HttpSession, method: String, url: String, user_agent: String, options: RequestOptions) -> HttpRequest {
let cookies = session.cookies.clone();
let user_agent = options.user_agent.or_else(|| Some(default_user_agent()));
let timeout = options.timeout.map(Duration::from_millis);
let mut request = HttpRequest {
@@ -113,10 +124,12 @@ impl HttpRequest {
headers: options.headers,
basic_auth: options.basic_auth,
user_agent,
follow_redirects: options.follow_redirects,
body: None,
timeout,
into_blob: options.into_blob,
proxy: options.proxy,
binary: options.binary,
};
if let Some(json) = options.json {
@@ -134,7 +147,7 @@ impl HttpRequest {
request
}
pub fn send(&self, state: &dyn State) -> Result<Response> {
pub fn send(&mut self, state: &dyn WebState) -> Result<Response> {
let mut url = self.url.parse::<Uri>()?;
// set query string
@@ -143,37 +156,9 @@ impl HttpRequest {
}
// start setting up request
let mut req = Request::builder();
req.method(self.method.as_str());
req.uri(url);
let mut observed_headers = HashSet::new();
// set cookies
{
use chrootable_https::header::COOKIE;
let mut cookies = String::new();
for (key, value) in self.cookies.iter() {
if !cookies.is_empty() {
cookies += "; ";
}
cookies.push_str(&format!("{}={}", key, value));
}
if !cookies.is_empty() {
req.header(COOKIE, cookies.as_str());
observed_headers.insert(COOKIE.as_str().to_lowercase());
}
}
let mut req = self.mkrequest(self.method.as_str(), &url);
// add headers
if let Some(ref agent) = self.user_agent {
use chrootable_https::header::USER_AGENT;
req.header(USER_AGENT, agent.as_str());
observed_headers.insert(USER_AGENT.as_str().to_lowercase());
}
if let Some(ref auth) = self.basic_auth {
use chrootable_https::header::AUTHORIZATION;
let &(ref user, ref password) = auth;
@@ -181,9 +166,9 @@ impl HttpRequest {
let auth = base64::encode(&format!("{}:{}", user, password));
let auth = format!("Basic {}", auth);
req.header(AUTHORIZATION, auth.as_str());
observed_headers.insert(AUTHORIZATION.as_str().to_lowercase());
}
let mut observed_headers = HashSet::new();
if let Some(ref headers) = self.headers {
for (k, v) in headers {
req.header(k.as_str(), v.as_str());
@@ -210,25 +195,75 @@ impl HttpRequest {
},
None => Body::empty(),
};
let req = req.body(body)?;
let mut req = req.body(body)?;
debug!("Getting http client");
let client = state.http(&self.proxy)?;
// send request
debug!("Sending http request: {:?}", req);
let res = client.request(req)
.with_timeout(self.timeout)
.wait_for_response()?;
let res = loop {
// send request
debug!("Sending http request: {:?}", req);
let res = client.request(req)
.with_timeout(self.timeout)
.wait_for_response()?;
for cookie in &res.cookies {
HttpRequest::register_cookies_on_state(&self.session, state, cookie);
}
for cookie in &res.cookies {
HttpRequest::register_cookies_on_state(&self.session, state, cookie);
}
if self.follow_redirects > 0 && res.status >= 300 && res.status < 400 {
if let Some(location) = res.headers.get("location") {
let base = Url::parse(&url.to_string())?;
let joined = base.join(location)?;
url = joined.to_string().parse()?;
req = self.mkrequest("GET", &url).body(Body::empty())?;
self.follow_redirects -= 1;
continue;
}
}
break res;
};
Ok(res)
}
pub fn response_to_lua(&self, state: &dyn State, res: Response) -> Result<LuaMap> {
/// create a basic request, reusable when following redirects
fn mkrequest<T>(&self, method: &str, url: T) -> Builder
where Uri: HttpTryFrom<T>,
{
let mut req = Request::builder();
req.method(method);
req.uri(url);
self.attach_cookies(&mut req);
use chrootable_https::header::USER_AGENT;
req.header(USER_AGENT, self.user_agent.as_str());
req
}
fn attach_cookies(&self, req: &mut Builder) {
use chrootable_https::header::COOKIE;
let mut cookies = String::new();
for (key, value) in self.cookies.iter() {
if !cookies.is_empty() {
cookies += "; ";
}
// it's a write to a String, so panic if re-allocation fails is fine
write!(cookies, "{}={}", key, value).expect("out of memory");
}
if !cookies.is_empty() {
req.header(COOKIE, cookies.as_str());
}
}
pub fn response_to_lua<S>(&self, state: &S, res: Response) -> Result<LuaMap>
where S: WebState + BlobState
{
// map result to LuaMap
let mut resp = LuaMap::new();
resp.insert_num("status", f64::from(res.status));
@@ -247,6 +282,8 @@ impl HttpRequest {
let blob = Blob::create(res.body);
let id = state.register_blob(blob);
resp.insert_str("blob", id);
} else if self.binary {
resp.insert_serde("binary", &res.body[..])?;
} else {
resp.insert_str("text", String::from_utf8_lossy(&res.body));
}
@@ -254,7 +291,7 @@ impl HttpRequest {
Ok(resp)
}
fn register_cookies_on_state(session: &str, state: &dyn State, cookie: &str) {
fn register_cookies_on_state(session: &str, state: &dyn WebState, cookie: &str) {
let mut key = String::new();
let mut value = String::new();
let mut in_key = true;
@@ -280,9 +317,9 @@ impl HttpRequest {
}
}
impl Into<AnyLuaValue> for HttpRequest {
fn into(self) -> AnyLuaValue {
let v = serde_json::to_value(&self).unwrap();
impl From<HttpRequest> for AnyLuaValue {
fn from(req: HttpRequest) -> AnyLuaValue {
let v = serde_json::to_value(&req).unwrap();
LuaJsonValue::from(v).into()
}
}

View File

@@ -3,11 +3,11 @@ use crate::errors::*;
use crate::hlua::AnyLuaValue;
use crate::json::LuaJsonValue;
use crate::sockets::{Stream, SocketOptions};
use std::borrow::Cow;
use http::Request;
use serde::Deserialize;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::io;
use tungstenite::handshake::client::Request;
use tungstenite::protocol::{self, Message};
use url::Url;
@@ -45,19 +45,17 @@ pub struct WebSocket {
impl WebSocket {
pub fn negotiate(stream: Stream, url: Url, headers: Option<&HashMap<String, String>>) -> Result<WebSocket> {
let extra_headers = headers.map(|headers| {
headers.iter()
.map(|(k, v)| (Cow::Borrowed(k.as_str()), Cow::Borrowed(v.as_str())))
.collect()
});
let mut req = Request::get(url.to_string()); // TODO: don't re-parse here
let (sock, _resp) = tungstenite::client::client(
Request {
url,
extra_headers,
},
stream,
)?;
if let Some(headers) = headers {
for (k, v) in headers {
req = req.header(k, v);
}
}
let req = req.body(()).unwrap();
let (sock, _resp) = tungstenite::client::client(req, stream)?;
Ok(WebSocket {
sock,
})
@@ -93,9 +91,11 @@ impl WebSocket {
}
pub fn options(&self, options: &WebSocketOptions) -> Result<()> {
let mut o = SocketOptions::default();
o.read_timeout = options.read_timeout;
o.write_timeout = options.write_timeout;
let o = SocketOptions {
read_timeout: options.read_timeout,
write_timeout: options.write_timeout,
..Default::default()
};
o.apply(self.sock.get_ref())
}

View File

@@ -2,6 +2,7 @@ use crate::errors::*;
use crate::json::LuaJsonValue;
use crate::hlua::AnyLuaValue;
use serde::Serialize;
use std::collections::HashMap;
use xml::attribute::OwnedAttribute;
use xml::name::OwnedName;
@@ -13,6 +14,12 @@ pub struct XmlDocument {
pub children: Vec<XmlElement>,
}
impl Default for XmlDocument {
fn default() -> Self {
Self::new()
}
}
impl XmlDocument {
#[inline(always)]
pub fn new() -> XmlDocument {
@@ -55,7 +62,7 @@ pub fn decode(x: &str) -> Result<AnyLuaValue> {
}
#[inline]
fn append_text(stack: &mut Vec<XmlElement>, text: String) {
fn append_text(stack: &mut [XmlElement], text: String) {
if let Some(tail) = stack.last_mut() {
if let Some(prev) = tail.text.as_mut() {
prev.push_str(&text);
@@ -124,6 +131,7 @@ fn decode_raw(x: &str) -> Result<XmlDocument> {
#[cfg(test)]
mod tests {
use super::*;
use maplit::hashmap;
#[test]
fn verify_xml_decode_empty() {

View File

@@ -1,35 +1,42 @@
use crate::errors::*;
use std::fmt;
use crate::web;
use crate::config::Config;
use crate::utils;
use chrootable_https::{self, HttpClient, Body, Request, Uri};
use chrootable_https::http::request::Builder as RequestBuilder;
use chrootable_https::header::CONTENT_TYPE;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use maplit::hashmap;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use serde_json;
use sn0int_common::api::*;
use sn0int_common::{ModuleID, ApiResponse};
use crate::web;
use std::fmt;
pub struct Client {
server: String,
client: chrootable_https::Client<chrootable_https::Resolver>,
session: Option<String>,
os_version: String,
}
impl Client {
pub fn new(config: &Config) -> Result<Client> {
let client = match config.network.proxy {
Some(proxy) => chrootable_https::Client::with_socks5(proxy),
_ => chrootable_https::Client::with_system_resolver()?,
let client = if let Some(proxy) = config.network.proxy {
chrootable_https::Client::with_socks5(proxy)
} else {
chrootable_https::Client::with_system_resolver_v4()?
};
let os_version = match os_version::detect() {
Ok(v) => v.to_string(),
Err(_) => String::from("unknown"),
};
Ok(Client {
server: config.core.registry.clone(),
client,
session: None,
os_version,
})
}
@@ -37,8 +44,18 @@ impl Client {
self.session = Some(session.into());
}
#[inline]
pub fn random_session() -> String {
thread_rng().sample_iter(&Alphanumeric).take(32).collect()
utils::random_string(32)
}
#[inline]
fn user_agent(&self) -> String {
format!("sn0int/{}, {}, {}",
env!("CARGO_PKG_VERSION"),
embedded_triple::get(),
self.os_version,
)
}
pub fn request<T: DeserializeOwned + fmt::Debug>(&self, mut request: RequestBuilder, body: Body) -> Result<T> {
@@ -46,7 +63,7 @@ impl Client {
info!("Adding session token to request");
request.header("Auth", session.as_str());
}
request.header("User-Agent", web::default_user_agent());
request.header("User-Agent", self.user_agent());
let request = request.body(body)?;

View File

@@ -1,15 +1,15 @@
use structopt::clap::{AppSettings, Shell};
use sn0int_common::ModuleID;
use crate::cmd;
use crate::options;
use crate::workspaces::Workspace;
use structopt::StructOpt;
use structopt::clap::{AppSettings, Shell};
use sn0int_common::ModuleID;
#[derive(Debug, StructOpt)]
#[structopt(global_settings = &[AppSettings::ColoredHelp])]
pub struct Args {
/// Select a different workspace instead of the default
#[structopt(short="w", long="workspace")]
#[structopt(short="w", long="workspace", env="SN0INT_WORKSPACE")]
pub workspace: Option<Workspace>,
#[structopt(subcommand)]
@@ -18,10 +18,7 @@ pub struct Args {
impl Args {
pub fn is_sandbox(&self) -> bool {
match self.subcommand {
Some(SubCommand::Sandbox(_)) => true,
_ => false,
}
matches!(self.subcommand, Some(SubCommand::Sandbox(_)))
}
}
@@ -48,6 +45,9 @@ pub enum SubCommand {
/// Search in the registry
#[structopt(name="search")]
Search(Search),
/// The sn0int package manager
#[structopt(name="pkg")]
Pkg(cmd::pkg_cmd::Args),
/// Insert into the database
#[structopt(name="add")]
Add(cmd::add_cmd::Args),
@@ -66,18 +66,36 @@ pub enum SubCommand {
/// Exclude entities from scope
#[structopt(name="noscope")]
Noscope(cmd::noscope_cmd::Args),
/// Manage autoscope rules
Autoscope(cmd::autoscope_cmd::Args),
/// Manage autonoscope rules
Autonoscope(cmd::autonoscope_cmd::Args),
/// Rescope all entities based on autonoscope rules
Rescope(cmd::rescope_cmd::Args),
/// Manage workspaces
#[structopt(name="workspace")]
Workspace(cmd::workspace_cmd::Args),
/// Calendar
#[structopt(name="cal")]
Cal(cmd::cal_cmd::Args),
/// Notify
#[structopt(name="notify")]
Notify(cmd::notify_cmd::Args),
/// Verify blob storage for corrupt and dangling blobs
#[structopt(name="fsck")]
Fsck(cmd::fsck_cmd::Args),
/// Export a workspace for external processing
#[structopt(name="export")]
Export(cmd::export_cmd::Args),
/// Show statistics about your current workspace
#[structopt(name="stats")]
Stats(cmd::stats_cmd::Args),
/// Run a lua repl
#[structopt(name="repl")]
Repl,
/// Show paths of various file system locations
#[structopt(name="paths")]
Paths,
/// Generate shell completions
#[structopt(name="completions")]
Completions(Completions),
@@ -85,18 +103,11 @@ pub enum SubCommand {
#[derive(Debug, StructOpt)]
pub struct Run {
/// Execute a module that has been installed
pub module: String,
#[structopt(flatten)]
pub run: cmd::run_cmd::Args,
/// Run a module from a path
#[structopt(short="f", long="file")]
pub file: bool,
/// Run modules concurrently
#[structopt(short="j", long="threads", default_value="1")]
pub threads: usize,
/// Verbose logging, once to print inserts even if they don't add new
/// data, twice to activate the debug() function
#[structopt(short="v", long="verbose", parse(from_occurrences))]
pub verbose: u64,
/// Expose stdin to modules
#[structopt(long="stdin")]
pub stdin: bool,
@@ -118,12 +129,15 @@ pub struct Run {
/// Narrow down targeted entities
#[structopt(short="t", long="target")]
pub target: Option<String>,
/// Dump the sandbox init message to stdout instead of running a child process
#[structopt(long="dump-sandbox-init-msg")]
pub dump_sandbox_init_msg: bool,
}
#[derive(Debug, StructOpt)]
pub struct Sandbox {
/// This value is only used for process listings
label: String,
_label: String,
}
#[derive(Debug, StructOpt)]

View File

@@ -1,5 +1,4 @@
use crate::errors::*;
use opener;
use std::fs;
use std::path::PathBuf;
use std::thread;
@@ -11,7 +10,7 @@ use crate::term;
fn path() -> Result<PathBuf> {
let path = paths::data_dir()?;
let path = paths::sn0int_dir()?;
Ok(path.join("auth"))
}

View File

@@ -1,5 +1,5 @@
use crate::errors::*;
use crate::autonoscope::{Autonoscope, IntoRule, AutoRule, RulePrecision};
use crate::autonoscope::{Autonoscope, ToRule, AutoRule, RulePrecision};
use crate::models::*;
use std::convert::TryFrom;
@@ -41,6 +41,13 @@ impl TryFrom<Autonoscope> for DomainRule {
}
}
impl AutoRule<Domain> for DomainRule {
#[inline]
fn matches(&self, domain: &Domain) -> Result<bool> {
self.matches(domain.value.as_str())
}
}
impl AutoRule<NewDomain> for DomainRule {
#[inline]
fn matches(&self, domain: &NewDomain) -> Result<bool> {
@@ -48,6 +55,13 @@ impl AutoRule<NewDomain> for DomainRule {
}
}
impl AutoRule<Subdomain> for DomainRule {
#[inline]
fn matches(&self, domain: &Subdomain) -> Result<bool> {
self.matches(domain.value.as_str())
}
}
impl AutoRule<NewSubdomain> for DomainRule {
#[inline]
fn matches(&self, domain: &NewSubdomain) -> Result<bool> {
@@ -55,6 +69,18 @@ impl AutoRule<NewSubdomain> for DomainRule {
}
}
impl AutoRule<Url> for DomainRule {
#[inline]
fn matches(&self, url: &Url) -> Result<bool> {
let url = url.value.parse::<url::Url>()?;
if let Some(domain) = url.domain() {
self.matches(domain)
} else {
Ok(false)
}
}
}
impl AutoRule<NewUrl> for DomainRule {
#[inline]
fn matches(&self, url: &NewUrl) -> Result<bool> {
@@ -94,8 +120,8 @@ impl RulePrecision for DomainRule {
}
}
impl IntoRule for DomainRule {
fn into_rule(&self) -> (&'static str, String) {
impl ToRule for DomainRule {
fn to_rule(&self) -> (&'static str, String) {
("domain", self.to_string())
}
}

View File

@@ -1,5 +1,5 @@
use crate::errors::*;
use crate::autonoscope::{Autonoscope, IntoRule, AutoRule, RulePrecision};
use crate::autonoscope::{Autonoscope, ToRule, AutoRule, RulePrecision};
use crate::models::*;
use std::convert::TryFrom;
use std::net;
@@ -36,12 +36,25 @@ impl TryFrom<Autonoscope> for IpRule {
}
}
impl AutoRule<IpAddr> for IpRule {
fn matches(&self, ipaddr: &IpAddr) -> Result<bool> {
self.matches(ipaddr.value.as_str())
}
}
impl AutoRule<NewIpAddr> for IpRule {
fn matches(&self, ipaddr: &NewIpAddr) -> Result<bool> {
self.matches(ipaddr.value.as_str())
}
}
impl AutoRule<Port> for IpRule {
fn matches(&self, port: &Port) -> Result<bool> {
let addr = port.value.parse::<net::SocketAddr>()?;
self.matches(&addr.ip())
}
}
impl AutoRule<NewPort> for IpRule {
fn matches(&self, port: &NewPort) -> Result<bool> {
let addr = port.value.parse::<net::SocketAddr>()?;
@@ -49,15 +62,25 @@ impl AutoRule<NewPort> for IpRule {
}
}
fn match_netblock_str(network: &IpNetwork, netblock: &str) -> Result<bool> {
let range = netblock.parse::<ipnetwork::IpNetwork>()?;
if network.prefix() <= range.prefix() {
Ok(network.contains(range.ip()))
} else {
Ok(false)
}
}
impl AutoRule<Netblock> for IpRule {
fn matches(&self, netblock: &Netblock) -> Result<bool> {
match_netblock_str(&self.network, &netblock.value)
}
}
impl AutoRule<NewNetblock> for IpRule {
fn matches(&self, netblock: &NewNetblock) -> Result<bool> {
let range = netblock.value.parse::<ipnetwork::IpNetwork>()?;
if self.network.prefix() <= range.prefix() {
Ok(self.network.contains(range.ip()))
} else {
Ok(false)
}
match_netblock_str(&self.network, &netblock.value)
}
}
@@ -70,7 +93,7 @@ impl AutoRule<str> for IpRule {
impl AutoRule<net::IpAddr> for IpRule {
fn matches(&self, ipaddr: &net::IpAddr) -> Result<bool> {
Ok(self.network.contains(ipaddr.clone()))
Ok(self.network.contains(*ipaddr))
}
}
@@ -80,8 +103,8 @@ impl RulePrecision for IpRule {
}
}
impl IntoRule for IpRule {
fn into_rule(&self) -> (&'static str, String) {
impl ToRule for IpRule {
fn to_rule(&self) -> (&'static str, String) {
("ip", self.to_string())
}
}

View File

@@ -1,9 +1,9 @@
use crate::db::DatabaseSock;
use crate::errors::*;
use diesel;
use diesel::prelude::*;
use crate::schema::*;
use crate::models::*;
use lazy_static::lazy_static;
use std::cmp::Ordering;
use std::convert::{TryInto, TryFrom};
use std::str::FromStr;
@@ -47,6 +47,18 @@ fn sort_precision_desc<T: RulePrecision>(a: &T, b: &T) -> Ordering {
}
impl RuleSet {
pub fn domains(&self) -> &[Rule<DomainRule>] {
&self.domains
}
pub fn ips(&self) -> &[Rule<IpRule>] {
&self.ips
}
pub fn urls(&self) -> &[Rule<UrlRule>] {
&self.urls
}
pub fn load(db: &DatabaseSock) -> Result<Self> {
use crate::schema::autonoscope::dsl::*;
let rules = autonoscope.load::<Autonoscope>(db)?;
@@ -112,15 +124,15 @@ impl RuleSet {
match obj {
RuleType::Domain => {
self.domains.retain(|x| x.to_string().as_str() != rule);
self.db_delete(db, obj, &rule)?;
self.db_delete(db, obj, rule)?;
},
RuleType::Ip => {
self.ips.retain(|x| x.to_string().as_str() != rule);
self.db_delete(db, obj, &rule)?;
self.db_delete(db, obj, rule)?;
},
RuleType::Url => {
self.urls.retain(|x| x.to_string().as_str() != rule);
self.db_delete(db, obj, &rule)?;
self.db_delete(db, obj, rule)?;
},
}
Ok(())
@@ -143,10 +155,18 @@ impl RuleSet {
rules
}
pub fn is_empty(&self) -> bool {
self.domains.is_empty() && self.ips.is_empty() && self.urls.is_empty()
}
pub fn len(&self) -> usize {
self.domains.len() + self.ips.len() + self.urls.len()
}
#[inline]
fn push_rules_display<T: IntoRule>(output: &mut Vec<(&'static str, String, bool)>, rules: &[Rule<T>]) {
fn push_rules_display<T: ToRule>(output: &mut Vec<(&'static str, String, bool)>, rules: &[Rule<T>]) {
for rule in rules {
let (object, value) = rule.into_rule();
let (object, value) = rule.to_rule();
output.push((object, value, rule.scoped));
}
}
@@ -159,10 +179,8 @@ impl RuleSet {
Insert::Url(url) => {
if let Some(result) = Self::matches_any(&self.domains, url)? {
Some(result)
} else if let Some(result) = Self::matches_any(&self.urls, url)? {
Some(result)
} else {
None
Self::matches_any(&self.urls, url)?
}
},
// Insert::Email(email) => unimplemented!(),
@@ -176,7 +194,7 @@ impl RuleSet {
fn matches_any<T1, T2>(rules: &[Rule<T1>], object: &T2) -> Result<Option<bool>>
where T1: AutoRule<T2>,
T1: IntoRule,
T1: ToRule,
{
for rule in rules {
if rule.matches(object)? {
@@ -238,12 +256,12 @@ pub trait RulePrecision {
}
#[derive(Debug, PartialEq)]
pub struct Rule<T: IntoRule> {
pub struct Rule<T: ToRule> {
rule: T,
scoped: bool,
pub scoped: bool,
}
impl<T: IntoRule> Rule<T> {
impl<T: ToRule> Rule<T> {
pub fn new(rule: T, scoped: bool) -> Rule<T> {
Rule {
rule,
@@ -254,7 +272,7 @@ impl<T: IntoRule> Rule<T> {
// TODO: maybe drop this
use std::ops::Deref;
impl<T: IntoRule> Deref for Rule<T> {
impl<T: ToRule> Deref for Rule<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
@@ -262,23 +280,23 @@ impl<T: IntoRule> Deref for Rule<T> {
}
}
impl<T: IntoRule + RulePrecision> RulePrecision for Rule<T> {
impl<T: ToRule + RulePrecision> RulePrecision for Rule<T> {
fn precision(&self) -> usize {
self.rule.precision()
}
}
pub trait IntoRule {
fn into_rule(&self) -> (&'static str, String);
pub trait ToRule {
fn to_rule(&self) -> (&'static str, String);
}
impl<T: IntoRule> Into<NewAutonoscope> for &Rule<T> {
fn into(self) -> NewAutonoscope {
let (object, value) = self.rule.into_rule();
impl<T: ToRule> From<&Rule<T>> for NewAutonoscope {
fn from(rule: &Rule<T>) -> NewAutonoscope {
let (object, value) = rule.rule.to_rule();
NewAutonoscope {
object: object.to_string(),
value,
scoped: self.scoped,
scoped: rule.scoped,
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::errors::*;
use crate::autonoscope::{Autonoscope, IntoRule, AutoRule, RulePrecision};
use crate::autonoscope::{Autonoscope, ToRule, AutoRule, RulePrecision};
use crate::models::*;
use std::convert::TryFrom;
@@ -47,6 +47,12 @@ impl TryFrom<&str> for UrlRule {
}
// TODO: there is no way to write a rule that matches all urls
impl AutoRule<Url> for UrlRule {
fn matches(&self, url: &Url) -> Result<bool> {
self.matches(url.value.as_str())
}
}
impl AutoRule<NewUrl> for UrlRule {
fn matches(&self, url: &NewUrl) -> Result<bool> {
self.matches(url.value.as_str())
@@ -86,8 +92,8 @@ impl RulePrecision for UrlRule {
}
}
impl IntoRule for UrlRule {
fn into_rule(&self) -> (&'static str, String) {
impl ToRule for UrlRule {
fn to_rule(&self) -> (&'static str, String) {
("url", self.to_string())
}
}

View File

@@ -3,44 +3,13 @@ use crate::paths;
use crate::worker::{EventWithCallback, Event2};
use crate::workspaces::Workspace;
use blake2::VarBlake2b;
use digest::{Input, VariableOutput};
use bytes::Bytes;
use serde::ser::{Serialize, Serializer};
use serde::de::{self, Deserialize, Deserializer};
use std::fs;
use std::path::{Path, PathBuf};
use std::result;
use std::sync::mpsc;
#[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 = VarBlake2b::new(32).unwrap();
h.input(bytes);
Self::encode_hash(&h.vec_result())
}
#[inline]
fn encode_hash(bytes: &[u8]) -> String {
let x = bs58::encode(bytes).into_string();
format!("{:0<44}", x)
}
}
pub use sn0int_std::blobs::Blob;
impl EventWithCallback for Blob {
type Payload = ();
@@ -51,30 +20,6 @@ impl EventWithCallback for Blob {
}
}
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)
.map_err(de::Error::custom)?;
Ok(Blob::create(Bytes::from(bytes)))
}
}
pub struct BlobStorage {
path: PathBuf,
}
@@ -148,6 +93,14 @@ impl BlobStorage {
}
Ok(blobs)
}
pub fn stat(&self, id: &str) -> Result<u64> {
let path = self.join(id)?;
debug!("Stat-ing blob: {:?}", path);
let md = fs::metadata(path)
.context("Failed to stat blob")?;
Ok(md.len())
}
}
@@ -155,7 +108,6 @@ impl BlobStorage {
mod tests {
use super::*;
use tempfile;
use serde_json;
#[inline]
fn blob() -> (Bytes, Blob) {
@@ -163,15 +115,6 @@ mod tests {
(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_blobstorage_save() {
let dir = tempfile::tempdir().expect("tempdir");
@@ -204,38 +147,4 @@ mod tests {
assert!(result.is_err());
}
#[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");
}
}

Some files were not shown because too many files have changed in this diff Show More