mirror of
https://codeberg.org/slatian/service.echoip-slatecave.git
synced 2024-11-10 08:37:21 +01:00
Added a templating helper
This commit is contained in:
parent
b21aa5192f
commit
febcb8b02e
213
src/main.rs
213
src/main.rs
@ -3,6 +3,9 @@ use axum::{
|
|||||||
extract::State,
|
extract::State,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::Html,
|
response::Html,
|
||||||
|
response::IntoResponse,
|
||||||
|
response::Response,
|
||||||
|
response::Json,
|
||||||
Router,
|
Router,
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
@ -11,13 +14,15 @@ use trust_dns_resolver::{
|
|||||||
TokioAsyncResolver,
|
TokioAsyncResolver,
|
||||||
config::ResolverOpts,
|
config::ResolverOpts,
|
||||||
config::ResolverConfig,
|
config::ResolverConfig,
|
||||||
error::*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
mod simple_dns;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Clone, Copy)]
|
||||||
enum ResponseFormat {
|
enum ResponseFormat {
|
||||||
#[serde(rename="text/plain", alias="text")]
|
#[serde(rename="text/plain", alias="text")]
|
||||||
TextPlain,
|
TextPlain,
|
||||||
@ -30,10 +35,10 @@ enum ResponseFormat {
|
|||||||
impl ToString for ResponseFormat {
|
impl ToString for ResponseFormat {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ResponseFormat::TextPlain => "text/plain".to_string(),
|
ResponseFormat::TextPlain => "text/plain",
|
||||||
ResponseFormat::TextHtml => "text/html".to_string(),
|
ResponseFormat::TextHtml => "text/html",
|
||||||
ResponseFormat::ApplicationJson => "application/json".to_string(),
|
ResponseFormat::ApplicationJson => "application/json",
|
||||||
}
|
}.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,27 +51,47 @@ struct IpQuery {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
struct DigQuery {
|
struct DigQuery {
|
||||||
name: String,
|
name: String,
|
||||||
format: Option<ResponseFormat>
|
format: Option<ResponseFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
struct MxRecord {
|
struct IpResult {
|
||||||
preference: u16,
|
hostname: Option<String>,
|
||||||
exchange: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
struct TemplatingEngine {
|
||||||
struct DigResult {
|
tera: Tera,
|
||||||
a: Vec<IpAddr>,
|
|
||||||
aaaa: Vec<IpAddr>,
|
|
||||||
mx: Vec<MxRecord>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServiceSharedState {
|
struct ServiceSharedState {
|
||||||
tera: Tera,
|
templating_engine: TemplatingEngine,
|
||||||
dns_resolver: TokioAsyncResolver,
|
dns_resolver: TokioAsyncResolver,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum EchoipView {
|
||||||
|
Dig { query: DigQuery, result: simple_dns::DnsLookupResult },
|
||||||
|
Index { query: IpQuery, result: IpResult },
|
||||||
|
Ip { query: IpQuery, result: IpResult },
|
||||||
|
Message(String),
|
||||||
|
#[serde(rename="404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EchoipView {
|
||||||
|
fn template_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
EchoipView::Dig{..} => "dig",
|
||||||
|
EchoipView::Index{..} => "index",
|
||||||
|
EchoipView::Ip{..} => "ip",
|
||||||
|
EchoipView::Message(..) => "message",
|
||||||
|
EchoipView::NotFound => "404",
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Initalize Tera templates
|
// Initalize Tera templates
|
||||||
@ -94,7 +119,9 @@ async fn main() {
|
|||||||
|
|
||||||
// Initialize shared state
|
// Initialize shared state
|
||||||
let shared_state = Arc::new(ServiceSharedState{
|
let shared_state = Arc::new(ServiceSharedState{
|
||||||
|
templating_engine: TemplatingEngine{
|
||||||
tera: tera,
|
tera: tera,
|
||||||
|
},
|
||||||
dns_resolver: dns_resolver,
|
dns_resolver: dns_resolver,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,33 +141,53 @@ async fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn hello_world_handler() -> &'static str {
|
async fn hello_world_handler(
|
||||||
"Hello, there, you, awesome creature!"
|
State(arc_state): State<Arc<ServiceSharedState>>,
|
||||||
|
) -> Response {
|
||||||
|
let state = Arc::clone(&arc_state);
|
||||||
|
|
||||||
|
state.templating_engine.render_view(
|
||||||
|
ResponseFormat::TextPlain,
|
||||||
|
EchoipView::Message("Hello! There, You, Awesome Creature!".to_string())
|
||||||
|
).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn simple_reverse_dns_lookup(
|
impl TemplatingEngine {
|
||||||
resolver: &TokioAsyncResolver,
|
async fn render_view(
|
||||||
address: &IpAddr,
|
&self,
|
||||||
) -> Option<String> {
|
format: ResponseFormat,
|
||||||
let revese_res = resolver.reverse_lookup(*address);
|
view: EchoipView,
|
||||||
match revese_res.await {
|
) -> Response {
|
||||||
Ok(lookup) => {
|
match format {
|
||||||
for name in lookup {
|
ResponseFormat::TextHtml => {
|
||||||
return Some(name.to_string())
|
let template_name = view.template_name();
|
||||||
}
|
|
||||||
None
|
let mut context = tera::Context::new();
|
||||||
}
|
context.insert("view", &template_name);
|
||||||
|
//intented for shared macros
|
||||||
|
context.insert("format", &format.to_string());
|
||||||
|
context.insert("data", &view);
|
||||||
|
|
||||||
|
match self.tera.render(&(template_name+".html"), &context) {
|
||||||
|
Ok(html) => Html(html).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let kind = e.kind();
|
println!("There was an error while rendering index.html: {e:?}");
|
||||||
match kind {
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
ResolveErrorKind::NoRecordsFound { .. } => {
|
}
|
||||||
//Ignore, that just happens …
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
//TODO: Plain Text should have its own matcher
|
||||||
println!("Reverse lookup on {address} failed: {kind}");
|
ResponseFormat::ApplicationJson | ResponseFormat::TextPlain => {
|
||||||
|
match view {
|
||||||
|
EchoipView::Dig{result, ..} => {
|
||||||
|
Json(result).into_response()
|
||||||
|
},
|
||||||
|
EchoipView::Index{result, ..} | EchoipView::Ip{result, ..} => {
|
||||||
|
Json(result).into_response()
|
||||||
|
},
|
||||||
|
_ => Json(view).into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,96 +195,40 @@ async fn simple_reverse_dns_lookup(
|
|||||||
async fn handle_default_route(
|
async fn handle_default_route(
|
||||||
Query(ip_query): Query<IpQuery>,
|
Query(ip_query): Query<IpQuery>,
|
||||||
State(arc_state): State<Arc<ServiceSharedState>>,
|
State(arc_state): State<Arc<ServiceSharedState>>,
|
||||||
) -> Result<Html<String>,StatusCode> {
|
) -> Response {
|
||||||
|
|
||||||
let address = ip_query.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
let address = ip_query.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
||||||
let format = ip_query.format.unwrap_or(ResponseFormat::TextHtml);
|
let format = ip_query.format.unwrap_or(ResponseFormat::TextHtml);
|
||||||
let format_string = format.to_string();
|
|
||||||
|
|
||||||
let state = Arc::clone(&arc_state);
|
let state = Arc::clone(&arc_state);
|
||||||
|
|
||||||
// do reverse lookup
|
// do reverse lookup
|
||||||
let hostname = simple_reverse_dns_lookup(&state.dns_resolver, &address);
|
let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address);
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let result = IpResult{
|
||||||
context.insert("ip", &address);
|
hostname: hostname.await,
|
||||||
context.insert("format", &format_string);
|
};
|
||||||
context.insert("hostname", &hostname.await);
|
|
||||||
|
|
||||||
match state.tera.render("index.html", &context) {
|
state.templating_engine.render_view(
|
||||||
Ok(html) => Ok(Html(html)),
|
format,
|
||||||
Err(e) => {
|
EchoipView::Index{query: ip_query, result: result}
|
||||||
println!("There was an error while rendering index.html: {e}");
|
).await
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_dig_route(
|
async fn handle_dig_route(
|
||||||
Query(dig_query): Query<DigQuery>,
|
Query(dig_query): Query<DigQuery>,
|
||||||
State(arc_state): State<Arc<ServiceSharedState>>,
|
State(arc_state): State<Arc<ServiceSharedState>>,
|
||||||
) -> Result<Html<String>,StatusCode> {
|
) -> Response {
|
||||||
|
|
||||||
let state = Arc::clone(&arc_state);
|
let state = Arc::clone(&arc_state);
|
||||||
let name = dig_query.name;
|
let name = &dig_query.name;
|
||||||
|
let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml);
|
||||||
|
|
||||||
let ipv4_lookup_res = state.dns_resolver.ipv4_lookup(&name);
|
let dig_result = simple_dns::lookup(&state.dns_resolver, name, true).await;
|
||||||
let ipv6_lookup_res = state.dns_resolver.ipv6_lookup(&name);
|
|
||||||
let mx_lookup_res = state.dns_resolver.mx_lookup(&name);
|
|
||||||
|
|
||||||
let mut dig_result = DigResult{
|
state.templating_engine.render_view(
|
||||||
a: Vec::new(),
|
format,
|
||||||
aaaa: Vec::new(),
|
EchoipView::Dig{ query: dig_query, result: dig_result}
|
||||||
mx: Vec::new(),
|
).await
|
||||||
};
|
|
||||||
|
|
||||||
match ipv4_lookup_res.await {
|
|
||||||
Ok(lookup) => {
|
|
||||||
for address in lookup {
|
|
||||||
dig_result.a.push(std::net::IpAddr::V4(address));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("There was an error while looking A up {name}: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match ipv6_lookup_res.await {
|
|
||||||
Ok(lookup) => {
|
|
||||||
for address in lookup {
|
|
||||||
dig_result.aaaa.push(std::net::IpAddr::V6(address));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("There was an error while looking AAAA up {name}: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match mx_lookup_res.await {
|
|
||||||
Ok(lookup) => {
|
|
||||||
for mx in lookup {
|
|
||||||
dig_result.mx.push(MxRecord{
|
|
||||||
preference: mx.preference(),
|
|
||||||
exchange: mx.exchange().to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("There was an error while looking MX up {name}: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
|
||||||
context.insert("dig_query", &name);
|
|
||||||
context.insert("dig_result", &dig_result);
|
|
||||||
|
|
||||||
match state.tera.render("dig.html", &context) {
|
|
||||||
Ok(html) => Ok(Html(html)),
|
|
||||||
Err(e) => {
|
|
||||||
println!("There was an error while rendering index.html: {e}");
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
118
src/simple_dns.rs
Normal file
118
src/simple_dns.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* This module wraps the trust_dns_resolver library
|
||||||
|
* to generate results thaat are ready for serializing
|
||||||
|
* or templating.
|
||||||
|
* It does not aim to be reusable for any other purpose,
|
||||||
|
* the trust_dns_resolver library already does that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use trust_dns_resolver::{
|
||||||
|
TokioAsyncResolver,
|
||||||
|
error::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
|
||||||
|
/* Data Structures */
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DnsLookupResult {
|
||||||
|
a: Vec<IpAddr>,
|
||||||
|
aaaa: Vec<IpAddr>,
|
||||||
|
mx: Vec<MxRecord>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct MxRecord {
|
||||||
|
preference: u16,
|
||||||
|
exchange: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lookup Functions*/
|
||||||
|
|
||||||
|
pub async fn reverse_lookup(
|
||||||
|
resolver: &TokioAsyncResolver,
|
||||||
|
address: &IpAddr,
|
||||||
|
) -> Option<String> {
|
||||||
|
let revese_res = resolver.reverse_lookup(*address);
|
||||||
|
match revese_res.await {
|
||||||
|
Ok(lookup) => {
|
||||||
|
for name in lookup {
|
||||||
|
return Some(name.to_string())
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let kind = e.kind();
|
||||||
|
match kind {
|
||||||
|
ResolveErrorKind::NoRecordsFound { .. } => {
|
||||||
|
//Ignore, that just happens …
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Reverse lookup on {address} failed: {kind}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function takes a resolver, a domain name and returns a DnsLookupResult.
|
||||||
|
// If do_full_lookup is false only the A and AAAA (CNAMEs planned for the future)
|
||||||
|
// records will be fetched.
|
||||||
|
pub async fn lookup(
|
||||||
|
resolver: &TokioAsyncResolver,
|
||||||
|
name: &String,
|
||||||
|
do_full_lookup: bool,
|
||||||
|
) -> DnsLookupResult {
|
||||||
|
let ipv4_lookup_res = resolver.ipv4_lookup(name);
|
||||||
|
let ipv6_lookup_res = resolver.ipv6_lookup(name);
|
||||||
|
|
||||||
|
// initlize an empty lookup result
|
||||||
|
let mut dig_result: DnsLookupResult = Default::default();
|
||||||
|
|
||||||
|
match ipv4_lookup_res.await {
|
||||||
|
Ok(lookup) => {
|
||||||
|
for address in lookup {
|
||||||
|
dig_result.a.push(std::net::IpAddr::V4(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("There was an error while looking A up {name}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match ipv6_lookup_res.await {
|
||||||
|
Ok(lookup) => {
|
||||||
|
for address in lookup {
|
||||||
|
dig_result.aaaa.push(std::net::IpAddr::V6(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("There was an error while looking AAAA up {name}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if do_full_lookup {
|
||||||
|
let mx_lookup_res = resolver.mx_lookup(name);
|
||||||
|
|
||||||
|
match mx_lookup_res.await {
|
||||||
|
Ok(lookup) => {
|
||||||
|
for mx in lookup {
|
||||||
|
dig_result.mx.push(MxRecord{
|
||||||
|
preference: mx.preference(),
|
||||||
|
exchange: mx.exchange().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("There was an error while looking MX up {name}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dig_result
|
||||||
|
|
||||||
|
}
|
@ -1,39 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
{% set r = data.result %}
|
||||||
|
{% set q = data.query %}
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Dig: {{ dig_query }}</title>
|
<title>Dig: {{ q.name }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Lookup for: {{ dig_query }}</h1>
|
<h1>Lookup for: {{ q.name }}</h1>
|
||||||
{% if dig_result.a %}
|
{% if r.a %}
|
||||||
<p>A (IPv4) records:</p>
|
<p>A (IPv4) records:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for address in dig_result.a%}
|
{% for address in r.a%}
|
||||||
<li><code>{{address}}</code></li>
|
<li><code>{{address}}</code></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if dig_result.aaaa %}
|
{% if r.aaaa %}
|
||||||
<p>AAAA (IPv6) records:</p>
|
<p>AAAA (IPv6) records:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for address in dig_result.aaaa%}
|
{% for address in r.aaaa%}
|
||||||
<li><code>{{address}}</code></li>
|
<li><code>{{address}}</code></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if dig_result.mx %}
|
{% if r.mx %}
|
||||||
<p>MX (Mail Exchange) records:</p>
|
<p>MX (Mail Exchange) records:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for mx in dig_result.mx%}
|
{% for mx in r.mx%}
|
||||||
<li>{{mx.preference}} <code><a href="/dig?name={{mx.exchange}}">{{mx.exchange}}</a></code></li>
|
<li>{{mx.preference}} <code><a href="/dig?name={{mx.exchange}}">{{mx.exchange}}</a></code></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Your IP: {{ ip }}</title>
|
<title>Your IP: {{ data.query.ip }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Your IP-Address is: {{ ip }}</h1>
|
<h1>Your IP-Address is: {{ data.query.ip }}</h1>
|
||||||
<p>Your requested format was: <b>{{format}}</b></p>
|
<p>Your requested format was: <b>{{format}}</b></p>
|
||||||
{% if hostname %}
|
{% if hostname %}
|
||||||
<p>Hostname: <b>{{hostname}}</b></p>
|
<p>Hostname: <b>{{data.result.hostname}}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user