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