First commit
This commit is contained in:
commit
58f33cb5ed
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
1285
Cargo.lock
generated
Normal file
1285
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"envoy",
|
||||
"envoy-models",
|
||||
"examples"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["perp"]
|
||||
license = "Apache License 2.0"
|
||||
repository = "https://git.supernets.org/perp/envoy"
|
||||
description = "Shodan API wrapper made in Rust"
|
||||
keywords = ["shodan", "shodan-api", "envoy"]
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
78
README.md
Normal file
78
README.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Envoy
|
||||
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
Envoy is a Shodan API wrapper made in Rust
|
||||
|
||||
## Examples
|
||||
```
|
||||
cargo run --example basic <key>
|
||||
cargo run --example proxy <key> <proxy>
|
||||
```
|
||||
|
||||
## API implementation
|
||||
|
||||
[Shodan API documentation](https://developer.shodan.io/api)
|
||||
|
||||
#### Search Methods
|
||||
- [x] /shodan/host/{ip}
|
||||
- [x] /shodan/host/count
|
||||
- [ ] /shodan/host/search
|
||||
- [x] /shodan/host/search/facets
|
||||
- [x] /shodan/host/search/filters
|
||||
- [x] /shodan/host/search/tokens
|
||||
|
||||
#### On-Demand Scanning
|
||||
- [x] /shodan/ports
|
||||
- [x] /shodan/protocols
|
||||
- [ ] /shodan/scan
|
||||
- [ ] /shodan/scan/internet
|
||||
- [x] /shodan/scan/{id}
|
||||
|
||||
#### Network Alerts
|
||||
- [ ] /shodan/alert
|
||||
- [ ] /shodan/alert/{id}/info
|
||||
- [ ] /shodan/alert/{id}
|
||||
- [ ] /shodan/alert/info
|
||||
- [ ] /shodan/alert/triggers
|
||||
- [ ] /shodan/alert/{id}/trigger/{trigger}
|
||||
- [ ] /shodan/alert/{id}/trigger/{trigger}/ignore/{service}
|
||||
- [ ] /shodan/alert/{id}/notifier/{notifier_id}
|
||||
|
||||
#### Notifiers
|
||||
- [ ] /notifier
|
||||
- [ ] /notifier/provider
|
||||
- [ ] /notifier/{id}
|
||||
|
||||
#### Directory Methods
|
||||
- [x] /shodan/query
|
||||
- [x] /shodan/query/search
|
||||
- [x] /shodan/query/tags
|
||||
|
||||
#### Bulk Data
|
||||
- [ ] /shodan/data
|
||||
- [ ] /shodan/data/{dataset}
|
||||
|
||||
#### Manage Organization
|
||||
- [ ] /org
|
||||
- [ ] /org/member/{user}
|
||||
|
||||
#### Account Methods
|
||||
- [x] /account/profile
|
||||
|
||||
#### DNS Methods
|
||||
- [x] /dns/domain/{domain}
|
||||
- [x] /dns/resolve
|
||||
- [x] /dns/reverse
|
||||
|
||||
#### Utility Methods
|
||||
- [x] /tools/httpheaders
|
||||
- [x] /tools/myip
|
||||
|
||||
### API Status Methods
|
||||
- [x] /api-info
|
||||
|
||||
### Other
|
||||
- [ ] Document examples for method
|
||||
|
||||
## Disclaimer
|
||||
###### Developers are not responsible for any misuse
|
14
envoy-models/Cargo.toml
Normal file
14
envoy-models/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "envoy-models"
|
||||
version = "1.7.0"
|
||||
edition = "2021"
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.4", features = ["json"] }
|
||||
serde = { version = "1.0.200", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
thiserror = "1.0.59"
|
2
envoy-models/src/account/mod.rs
Normal file
2
envoy-models/src/account/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod profile;
|
||||
pub use profile::Profile;
|
10
envoy-models/src/account/profile.rs
Normal file
10
envoy-models/src/account/profile.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Profile response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Profile {
|
||||
pub member: bool,
|
||||
pub credits: i64,
|
||||
pub display_name: Option<String>,
|
||||
pub created: String,
|
||||
}
|
5
envoy-models/src/directory/mod.rs
Normal file
5
envoy-models/src/directory/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod query;
|
||||
pub use query::Query;
|
||||
|
||||
mod tags;
|
||||
pub use tags::Tags;
|
19
envoy-models/src/directory/query.rs
Normal file
19
envoy-models/src/directory/query.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Query response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Query {
|
||||
pub matches: Vec<Matches>,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
/// Matches response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Matches {
|
||||
pub votes: i64,
|
||||
pub description: String,
|
||||
pub tags: Vec<String>,
|
||||
pub timestamp: String,
|
||||
pub title: String,
|
||||
pub query: String,
|
||||
}
|
15
envoy-models/src/directory/tags.rs
Normal file
15
envoy-models/src/directory/tags.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Tags response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Tags {
|
||||
pub matches: Vec<Matches>,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
/// Matches response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Matches {
|
||||
pub count: i64,
|
||||
pub value: String,
|
||||
}
|
20
envoy-models/src/dns/domain.rs
Normal file
20
envoy-models/src/dns/domain.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Domain response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Domain {
|
||||
pub domain: String,
|
||||
pub tags: Vec<String>,
|
||||
pub data: Vec<Data>,
|
||||
pub subdomains: Vec<String>,
|
||||
pub more: bool,
|
||||
}
|
||||
|
||||
/// Data response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Data {
|
||||
pub subdomain: String,
|
||||
pub r#type: String,
|
||||
pub value: String,
|
||||
pub last_seen: String,
|
||||
}
|
2
envoy-models/src/dns/mod.rs
Normal file
2
envoy-models/src/dns/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod domain;
|
||||
pub use domain::Domain;
|
42
envoy-models/src/lib.rs
Normal file
42
envoy-models/src/lib.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
pub mod account;
|
||||
pub mod directory;
|
||||
pub mod dns;
|
||||
pub mod scanning;
|
||||
pub mod search;
|
||||
pub mod status;
|
||||
pub mod utility;
|
||||
|
||||
/// API error response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub error: ErrorType,
|
||||
}
|
||||
|
||||
/// API error type
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum ErrorType {
|
||||
#[serde(rename(deserialize = "Invalid IP"))]
|
||||
InvalidIP,
|
||||
#[serde(rename(deserialize = "No information available for that IP."))]
|
||||
UnknownIP,
|
||||
#[serde(rename(deserialize = "Empty search query"))]
|
||||
EmptyQuery,
|
||||
}
|
||||
|
||||
/// Client error
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// API error
|
||||
#[error("API returned an error: {0:#?}")]
|
||||
API(ErrorResponse),
|
||||
|
||||
/// Serde JSON error
|
||||
#[error("Serde returned an error: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
|
||||
/// HTTP error
|
||||
#[error("Reqwest returned an error: {0:#?}")]
|
||||
HttpRequest(#[from] reqwest::Error),
|
||||
}
|
5
envoy-models/src/scanning/mod.rs
Normal file
5
envoy-models/src/scanning/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod scan;
|
||||
pub use scan::Scan;
|
||||
|
||||
mod scans;
|
||||
pub use scans::Scans;
|
10
envoy-models/src/scanning/scan.rs
Normal file
10
envoy-models/src/scanning/scan.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Scan response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Scan {
|
||||
pub count: i64,
|
||||
pub status: String,
|
||||
pub id: String,
|
||||
pub created: String,
|
||||
}
|
20
envoy-models/src/scanning/scans.rs
Normal file
20
envoy-models/src/scanning/scans.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Scans response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Scans {
|
||||
pub matches: Vec<Matches>,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
/// Matches response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Matches {
|
||||
pub status: String,
|
||||
pub created: String,
|
||||
pub status_check: String,
|
||||
pub credits_left: i64,
|
||||
pub api_key: String,
|
||||
pub id: String,
|
||||
pub size: i64,
|
||||
}
|
23
envoy-models/src/search/count.rs
Normal file
23
envoy-models/src/search/count.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Count response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Count {
|
||||
pub matches: Vec<String>,
|
||||
pub facets: Option<Facets>,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
/// Facets response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Facets {
|
||||
pub org: Vec<Facet>,
|
||||
pub os: Vec<Facet>,
|
||||
}
|
||||
|
||||
/// Facet response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Facet {
|
||||
pub count: i64,
|
||||
pub value: String,
|
||||
}
|
95
envoy-models/src/search/host.rs
Normal file
95
envoy-models/src/search/host.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Host response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Host {
|
||||
pub region_code: Option<String>,
|
||||
pub ip: i64,
|
||||
pub postal_code: Option<String>,
|
||||
pub country_code: String,
|
||||
pub city: Option<String>,
|
||||
pub dma_code: Option<String>,
|
||||
pub last_update: String,
|
||||
pub latitude: f64,
|
||||
pub tags: Vec<String>,
|
||||
pub area_code: Option<String>,
|
||||
pub country_name: String,
|
||||
pub hostnames: Vec<String>,
|
||||
pub org: String,
|
||||
pub data: Vec<Data>,
|
||||
pub asn: String,
|
||||
pub isp: String,
|
||||
pub longitude: f64,
|
||||
pub country_code3: Option<String>,
|
||||
pub domains: Vec<String>,
|
||||
pub ip_str: String,
|
||||
pub os: Option<String>,
|
||||
pub ports: Vec<i64>,
|
||||
}
|
||||
|
||||
/// Data response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Data {
|
||||
#[serde(rename(deserialize = "_shodan"))]
|
||||
pub shodan: Shodan,
|
||||
pub hash: i64,
|
||||
pub os: Option<String>,
|
||||
pub opts: Opts,
|
||||
pub ip: i64,
|
||||
pub isp: String,
|
||||
pub port: i64,
|
||||
pub hostnames: Vec<String>,
|
||||
pub location: Location,
|
||||
pub dns: Option<Dns>,
|
||||
pub timestamp: String,
|
||||
pub domains: Vec<String>,
|
||||
pub org: String,
|
||||
pub data: String,
|
||||
pub asn: String,
|
||||
pub transport: String,
|
||||
pub ip_str: String,
|
||||
}
|
||||
|
||||
/// Shodan response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Shodan {
|
||||
pub id: String,
|
||||
pub options: Option<Options>,
|
||||
pub ptr: Option<bool>,
|
||||
pub module: String,
|
||||
pub crawler: String,
|
||||
}
|
||||
|
||||
/// Options response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Options {}
|
||||
|
||||
/// Opts response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Opts {
|
||||
pub raw: Option<String>,
|
||||
}
|
||||
|
||||
/// Location response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Location {
|
||||
pub city: Option<String>,
|
||||
pub region_code: Option<String>,
|
||||
pub area_code: Option<String>,
|
||||
pub longitude: f64,
|
||||
pub country_code3: Option<String>,
|
||||
pub country_name: String,
|
||||
pub postal_code: Option<String>,
|
||||
pub dma_code: Option<String>,
|
||||
pub country_code: String,
|
||||
pub latitude: f64,
|
||||
}
|
||||
|
||||
/// DNS response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Dns {
|
||||
pub resolver_hostname: Option<String>,
|
||||
pub recursive: bool,
|
||||
pub resolver_id: Option<String>,
|
||||
pub software: Option<String>,
|
||||
}
|
8
envoy-models/src/search/mod.rs
Normal file
8
envoy-models/src/search/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
mod count;
|
||||
pub use count::Count;
|
||||
|
||||
mod host;
|
||||
pub use host::Host;
|
||||
|
||||
mod tokens;
|
||||
pub use tokens::Tokens;
|
16
envoy-models/src/search/tokens.rs
Normal file
16
envoy-models/src/search/tokens.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Tokens response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Tokens {
|
||||
pub attributes: Attributes,
|
||||
pub errors: Vec<String>,
|
||||
pub string: String,
|
||||
pub filters: Vec<String>,
|
||||
}
|
||||
|
||||
/// Attributes response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Attributes {
|
||||
pub ports: Vec<i64>,
|
||||
}
|
23
envoy-models/src/status/info.rs
Normal file
23
envoy-models/src/status/info.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Info response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Info {
|
||||
pub scan_credits: i64,
|
||||
pub usage_limits: UsageLimits,
|
||||
pub plan: String,
|
||||
pub https: bool,
|
||||
pub unlocked: bool,
|
||||
pub query_credits: i64,
|
||||
pub monitored_ips: i64,
|
||||
pub unlocked_left: i64,
|
||||
pub telnet: bool,
|
||||
}
|
||||
|
||||
/// Usage limits response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UsageLimits {
|
||||
pub scan_credits: i64,
|
||||
pub query_credits: i64,
|
||||
pub monitored_ips: i64,
|
||||
}
|
2
envoy-models/src/status/mod.rs
Normal file
2
envoy-models/src/status/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod info;
|
||||
pub use info::Info;
|
32
envoy-models/src/utility/http_headers.rs
Normal file
32
envoy-models/src/utility/http_headers.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// HTTP headers response
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct HttpHeaders {
|
||||
#[serde(rename(deserialize = "Content-Length"))]
|
||||
pub content_length: String,
|
||||
#[serde(rename(deserialize = "Cf-Visitor"))]
|
||||
pub cf_visitor: String,
|
||||
#[serde(rename(deserialize = "Accept-Encoding"))]
|
||||
pub accept_encoding: String,
|
||||
#[serde(rename(deserialize = "X-Forwarded-For"))]
|
||||
pub x_forwarded_for: String,
|
||||
#[serde(rename(deserialize = "Host"))]
|
||||
pub host: String,
|
||||
#[serde(rename(deserialize = "User-Agent"))]
|
||||
pub user_agent: String,
|
||||
#[serde(rename(deserialize = "Connection"))]
|
||||
pub connection: String,
|
||||
#[serde(rename(deserialize = "X-Forwarded-Proto"))]
|
||||
pub x_forwarded_proto: String,
|
||||
#[serde(rename(deserialize = "Accept"))]
|
||||
pub accept: String,
|
||||
#[serde(rename(deserialize = "Cdn-Loop"))]
|
||||
pub cdn_loop: String,
|
||||
#[serde(rename(deserialize = "Cf-Connecting-Ip"))]
|
||||
pub cf_connecting_ip: String,
|
||||
#[serde(rename(deserialize = "Cf-Ray"))]
|
||||
pub cf_ray: String,
|
||||
#[serde(rename(deserialize = "Content-Type"))]
|
||||
pub content_type: String,
|
||||
}
|
2
envoy-models/src/utility/mod.rs
Normal file
2
envoy-models/src/utility/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod http_headers;
|
||||
pub use http_headers::HttpHeaders;
|
14
envoy/Cargo.toml
Normal file
14
envoy/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "envoy"
|
||||
version = "1.7.0"
|
||||
edition = "2021"
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.80"
|
||||
reqwest = { version = "0.12.4", default-features = false, features = ["json", "socks"] }
|
||||
serde = { version = "1.0.200", features = ["derive"] }
|
||||
envoy-models = { path = "../envoy-models/" }
|
1
envoy/src/account/mod.rs
Normal file
1
envoy/src/account/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod profile;
|
19
envoy/src/account/profile.rs
Normal file
19
envoy/src/account/profile.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use envoy_models::account::Profile;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch account profile information
|
||||
pub async fn fetch_profile(&self) -> Result<Profile> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/account/profile", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
3
envoy/src/directory/mod.rs
Normal file
3
envoy/src/directory/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod search;
|
||||
pub(crate) mod tags;
|
33
envoy/src/directory/query.rs
Normal file
33
envoy/src/directory/query.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use envoy_models::directory::Query;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch the saved queries
|
||||
/// # Arguments
|
||||
/// * `page` - Current page, contains 10 items (Default: 0)
|
||||
/// * `sort` - Sort by property: votes or timestamp (Default: votes)
|
||||
/// * `order` - Order by property: asc or desc (Default: desc)
|
||||
pub async fn fetch_query(
|
||||
&self,
|
||||
page: Option<i64>,
|
||||
sort: Option<&str>,
|
||||
order: Option<&str>,
|
||||
) -> Result<Query> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/query", self.base_url))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("page", page.unwrap_or_default().to_string()),
|
||||
("sort", sort.unwrap_or("votes").to_string()),
|
||||
("order", order.unwrap_or("desc").to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
26
envoy/src/directory/search.rs
Normal file
26
envoy/src/directory/search.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use envoy_models::directory::Query;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a search query
|
||||
/// # Arguments
|
||||
/// * `query` - Query to search
|
||||
/// * `page` - Current page, contains 10 items (Default: 0)
|
||||
pub async fn fetch_search_query(&self, query: &str, page: Option<i64>) -> Result<Query> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/query/search", self.base_url))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("query", query.to_string()),
|
||||
("page", page.unwrap_or_default().to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
24
envoy/src/directory/tags.rs
Normal file
24
envoy/src/directory/tags.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use envoy_models::directory::Tags;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch query tags
|
||||
/// # Arguments
|
||||
/// * `size` - Amount of tags (Default: 10)
|
||||
pub async fn fetch_query_tags(&self, size: Option<i64>) -> Result<Tags> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/query/tags", self.base_url))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("size", size.unwrap_or(10).to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
35
envoy/src/dns/domain.rs
Normal file
35
envoy/src/dns/domain.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use envoy_models::dns::Domain;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch subdomains and DNS entries for a domain
|
||||
/// # Arguments
|
||||
/// * `domain` - Domain to search
|
||||
/// * `history` - Show old DNS records (Default: false)
|
||||
/// * `type` - Type of DNS record: A, AAAA, CNAME, NS, SOA, MX, TXT (Default: A)
|
||||
/// * `page` - Current page, contains 100 items (Default: 0)
|
||||
pub async fn fetch_domain(
|
||||
&self,
|
||||
domain: &str,
|
||||
history: Option<bool>,
|
||||
r#type: Option<&str>,
|
||||
page: Option<i64>,
|
||||
) -> Result<Domain> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/dns/domain/{}", self.base_url, domain))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("history", history.unwrap_or(false).to_string()),
|
||||
("type", r#type.unwrap_or("A").to_string()),
|
||||
("page", page.unwrap_or_default().to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
3
envoy/src/dns/mod.rs
Normal file
3
envoy/src/dns/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) mod domain;
|
||||
pub(crate) mod resolve;
|
||||
pub(crate) mod reverse;
|
24
envoy/src/dns/resolve.rs
Normal file
24
envoy/src/dns/resolve.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch dns resolve information
|
||||
/// # Arguments
|
||||
/// * `hostnames` - Hostnames to search e.g google.com,bing.com
|
||||
pub async fn fetch_dns_resolve(&self, hostnames: &str) -> Result<HashMap<String, String>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/dns/resolve", self.base_url))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("hostnames", hostnames.to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
21
envoy/src/dns/reverse.rs
Normal file
21
envoy/src/dns/reverse.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch dns reverse information
|
||||
/// # Arguments
|
||||
/// * `ips` - IP addresses to search e.g 1.1.1.1,8.8.8.8
|
||||
pub async fn fetch_dns_reverse(&self, ips: &str) -> Result<HashMap<String, Vec<String>>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/dns/reverse", self.base_url))
|
||||
.query(&[("key", self.key.to_owned()), ("ips", ips.to_string())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
78
envoy/src/lib.rs
Normal file
78
envoy/src/lib.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use async_trait::async_trait;
|
||||
use envoy_models::Error;
|
||||
|
||||
pub(crate) mod account;
|
||||
pub(crate) mod directory;
|
||||
pub(crate) mod dns;
|
||||
pub(crate) mod scanning;
|
||||
pub(crate) mod search;
|
||||
pub(crate) mod status;
|
||||
pub(crate) mod utility;
|
||||
|
||||
pub(crate) mod prelude {
|
||||
pub(crate) use crate::{ResponseExt, Result, Shodan};
|
||||
}
|
||||
|
||||
/// Custom error Result type
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Base URL of Shodan API
|
||||
pub const BASE_URL: &str = "https://api.shodan.io";
|
||||
|
||||
/// Handle response error
|
||||
#[async_trait]
|
||||
trait ResponseExt {
|
||||
async fn process_error(self) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Match response status code
|
||||
/// and handle the error
|
||||
#[async_trait]
|
||||
impl ResponseExt for reqwest::Response {
|
||||
async fn process_error(self) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self.status().as_u16() {
|
||||
200 => Ok(self),
|
||||
_ => Err(Error::API(self.json().await?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shodan client for interacting with the API
|
||||
#[derive(Debug)]
|
||||
pub struct Shodan {
|
||||
key: String,
|
||||
base_url: String,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl Shodan {
|
||||
/// Constructs a new `Shodan` client
|
||||
pub fn new(key: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_owned(),
|
||||
base_url: BASE_URL.to_owned(),
|
||||
client: reqwest::ClientBuilder::new()
|
||||
.user_agent(concat!("Envoy", "/", env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new `Shodan` client with a proxy
|
||||
pub fn new_with_proxy(key: &str, proxy: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_owned(),
|
||||
base_url: BASE_URL.to_owned(),
|
||||
client: reqwest::ClientBuilder::new()
|
||||
.user_agent(concat!("Envoy", "/", env!("CARGO_PKG_VERSION")))
|
||||
.proxy(reqwest::Proxy::all(proxy).expect("Could not connect to proxy"))
|
||||
.build()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
4
envoy/src/scanning/mod.rs
Normal file
4
envoy/src/scanning/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub(crate) mod ports;
|
||||
pub(crate) mod protocols;
|
||||
pub(crate) mod scan;
|
||||
pub(crate) mod scans;
|
17
envoy/src/scanning/ports.rs
Normal file
17
envoy/src/scanning/ports.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of ports
|
||||
pub async fn fetch_ports(&self) -> Result<Vec<i64>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/ports", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
19
envoy/src/scanning/protocols.rs
Normal file
19
envoy/src/scanning/protocols.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of protocols
|
||||
pub async fn fetch_protocols(&self) -> Result<HashMap<String, String>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/protocols", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
26
envoy/src/scanning/scan.rs
Normal file
26
envoy/src/scanning/scan.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use envoy_models::scanning::Scan;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a scan
|
||||
/// # Arguments
|
||||
/// * `id` - Scan ID
|
||||
/// # Status
|
||||
/// * SUBMITTING
|
||||
/// * QUEUE
|
||||
/// * PROCESSING
|
||||
/// * DONE
|
||||
pub async fn fetch_scan(&self, id: &str) -> Result<Scan> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/scan/{}", self.base_url, id))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
19
envoy/src/scanning/scans.rs
Normal file
19
envoy/src/scanning/scans.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use envoy_models::scanning::Scans;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of scans
|
||||
pub async fn fetch_scans(&self) -> Result<Scans> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/scans", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
26
envoy/src/search/count.rs
Normal file
26
envoy/src/search/count.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use envoy_models::search::Count;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch count of a query
|
||||
/// # Arguments
|
||||
/// * `query` - Query to search
|
||||
/// * `facets` - Facets to search
|
||||
pub async fn fetch_count(&self, query: &str, facets: Option<&str>) -> Result<Count> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/host/search/count", self.base_url))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("query", query.to_owned()),
|
||||
("facets", facets.unwrap_or_default().to_owned()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
17
envoy/src/search/facets.rs
Normal file
17
envoy/src/search/facets.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of facets
|
||||
pub async fn fetch_facets(&self) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/host/search/facets", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
17
envoy/src/search/filters.rs
Normal file
17
envoy/src/search/filters.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of filters
|
||||
pub async fn fetch_filters(&self) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/host/search/filters", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
32
envoy/src/search/host.rs
Normal file
32
envoy/src/search/host.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use envoy_models::search::Host;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch host information
|
||||
/// # Arguments
|
||||
/// * `ip` - Host IP address
|
||||
/// * `history` - Show old records (Default: false)
|
||||
/// * `minify` - Minify the response (Default: false)
|
||||
pub async fn fetch_host(
|
||||
&self,
|
||||
ip: &str,
|
||||
history: Option<bool>,
|
||||
minify: Option<bool>,
|
||||
) -> Result<Host> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/host/{}", self.base_url, ip))
|
||||
.query(&[
|
||||
("key", self.key.to_owned()),
|
||||
("history", history.unwrap_or_default().to_string()),
|
||||
("minify", minify.unwrap_or_default().to_string()),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
5
envoy/src/search/mod.rs
Normal file
5
envoy/src/search/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub(crate) mod count;
|
||||
pub(crate) mod facets;
|
||||
pub(crate) mod filters;
|
||||
pub(crate) mod host;
|
||||
pub(crate) mod tokens;
|
21
envoy/src/search/tokens.rs
Normal file
21
envoy/src/search/tokens.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use envoy_models::search::Tokens;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch a list of tokens
|
||||
/// # Arguments
|
||||
/// * `query` - Query to search
|
||||
pub async fn fetch_tokens(&self, query: &str) -> Result<Tokens> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/shodan/host/search/tokens", self.base_url))
|
||||
.query(&[("key", self.key.to_owned()), ("query", query.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
19
envoy/src/status/info.rs
Normal file
19
envoy/src/status/info.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use envoy_models::status::Info;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch plan information
|
||||
pub async fn fetch_info(&self) -> Result<Info> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/api-info", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
1
envoy/src/status/mod.rs
Normal file
1
envoy/src/status/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod info;
|
19
envoy/src/utility/http_headers.rs
Normal file
19
envoy/src/utility/http_headers.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use envoy_models::utility::HttpHeaders;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch HTTP headers
|
||||
pub async fn fetch_http_headers(&self) -> Result<HttpHeaders> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/tools/httpheaders", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
2
envoy/src/utility/mod.rs
Normal file
2
envoy/src/utility/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub(crate) mod http_headers;
|
||||
pub(crate) mod my_ip;
|
17
envoy/src/utility/my_ip.rs
Normal file
17
envoy/src/utility/my_ip.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
impl Shodan {
|
||||
/// Fetch my (your) current IP address
|
||||
pub async fn fetch_my_ip(&self) -> Result<String> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(format!("{}/tools/myip", self.base_url))
|
||||
.query(&[("key", self.key.to_owned())])
|
||||
.send()
|
||||
.await?
|
||||
.process_error()
|
||||
.await?
|
||||
.text()
|
||||
.await?)
|
||||
}
|
||||
}
|
17
examples/Cargo.toml
Normal file
17
examples/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
envoy = { path = "../envoy" }
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[[example]]
|
||||
name = "basic"
|
||||
path = "basic.rs"
|
||||
|
||||
[[example]]
|
||||
name = "proxy"
|
||||
path = "proxy.rs"
|
29
examples/basic.rs
Normal file
29
examples/basic.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::env;
|
||||
|
||||
use envoy::Shodan;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// CLI arguments
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
||||
// Missing arguments
|
||||
if args.len() < 2 {
|
||||
panic!("Missing arguments");
|
||||
}
|
||||
|
||||
// Create client
|
||||
let shodan = Shodan::new(&args[1]);
|
||||
|
||||
// Fetch profile
|
||||
let profile = shodan.fetch_profile().await.unwrap();
|
||||
println!("{:#?}", profile);
|
||||
|
||||
// Fetch information
|
||||
let info = shodan.fetch_info().await.unwrap();
|
||||
println!("{:#?}", info);
|
||||
|
||||
// Fetch tokens
|
||||
let tokens = shodan.fetch_tokens("Raspbian port:22").await.unwrap();
|
||||
println!("{:#?}", tokens);
|
||||
}
|
25
examples/proxy.rs
Normal file
25
examples/proxy.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::env;
|
||||
|
||||
use envoy::Shodan;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// CLI arguments
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
||||
// Missing arguments
|
||||
if args.len() < 2 {
|
||||
panic!("Missing arguments");
|
||||
}
|
||||
|
||||
// Create client
|
||||
let shodan = Shodan::new_with_proxy(&args[1], &args[2]);
|
||||
|
||||
// Fetch IP address
|
||||
let myip = shodan.fetch_my_ip().await.unwrap();
|
||||
println!("{:#?}", myip);
|
||||
|
||||
// Fetch headers
|
||||
let headers = shodan.fetch_http_headers().await.unwrap();
|
||||
println!("{:#?}", headers);
|
||||
}
|
Loading…
Reference in New Issue
Block a user