35 Commits

Author SHA1 Message Date
396bbdb348 Release 1.2.2 2023-12-10 10:18:31 +01:00
a582c74d18 urlencode queries to external services
and add crt.sh
2023-12-10 10:16:43 +01:00
e8a21ac95f Release 1.2.1 2023-12-09 23:26:12 +01:00
d706e7c614 Update to axum 0.7 2023-12-09 23:21:19 +01:00
0bffa0fd96 Update smaller dependencies 2023-12-09 12:01:00 +01:00
fb0ce1dc0b Update trust_dns to 23.2 2023-12-09 11:54:07 +01:00
a67631fa9b cargo update 2023-12-09 11:17:33 +01:00
636e10f786 Adapted to the new syncronous interface of the HumusEngine 2023-10-30 17:44:33 +01:00
0076db531a cargo update 2023-10-30 17:41:37 +01:00
64e639b0df Release 1.2.0 2023-10-30 01:56:18 +01:00
2f9f01e947 Better template documentation 2023-10-30 01:54:44 +01:00
f2e9e36e99 codename mycelium became lib-humus 2023-10-30 01:54:35 +01:00
912a119361 Made template loading logic reuseable 2023-10-29 20:52:32 +01:00
5adca4fb80 WIP moved templating engine to mycelium 2023-10-29 18:56:44 +01:00
5ac056ef99 Made sure the get_status and update_response callbacks are used correctly 2023-10-29 18:20:57 +01:00
51aa05fe13 Added query settings to the generaliued templating mechanism 2023-10-29 18:10:57 +01:00
de179ea7fa Proper MimeType handling with mycelium 2023-10-29 16:51:43 +01:00
bfa383ddbe Fixed template data piping 2023-10-29 15:36:16 +01:00
a33473fdc9 Moved to a more genral implementation for the response format. 2023-10-29 15:23:47 +01:00
20fb7ee2ff First step to detaching the templating from the logic. 2023-10-29 13:50:22 +01:00
c5a7597561 cargo update 2023-10-29 10:55:40 +01:00
c56cc6edbd Version bump to 1.1.3 2023-10-08 09:27:38 +02:00
5c74de5685 Switch a lot of Strings to Arc<str> 2023-10-08 09:12:06 +02:00
223abdd804 cargo update
This one only removed dependencies 🥳
2023-10-08 08:09:44 +02:00
639d4579e9 Fixed the url pointing to the google dns information^W marketing 2023-08-08 23:15:43 +02:00
4b3a8d5e08 Fixed dns resolver information templates 2023-08-08 23:06:55 +02:00
53da9023da cargo update 2023-08-07 21:59:25 +02:00
4876fb7ea0 Updated README 2023-08-07 21:58:42 +02:00
2aa6baaa57 Removed dns search functionality completely
I don't see it working and not being annoying
2023-08-07 21:52:31 +02:00
daa68bbd5d Reenabled search prevention fqdn dot 2023-08-07 21:42:56 +02:00
231e46a688 Disabled domain search as it caused problems 2023-08-07 21:40:22 +02:00
2fe1b69174 Beeter communication of dns errors 2023-08-07 21:09:14 +02:00
2e1f6a77ac Slightly better handling of invalid domain names 2023-08-07 20:05:25 +02:00
1fe59d24d5 Discard additional responses instead of misattributing them 2023-08-07 19:03:18 +02:00
51d7954d71 Make the sitename render as inline-block to prevent accidental clicking 2023-08-06 13:23:41 +02:00
19 changed files with 1005 additions and 1085 deletions

1304
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,31 @@
[package] [package]
name = "echoip-slatecave" name = "echoip-slatecave"
version = "0.1.0" version = "1.2.2"
edition = "2021" edition = "2021"
authors = ["Slatian <baschdel@disroot.org>"] authors = ["Slatian <baschdel@disroot.org>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = { version = "0.6", features = ["macros", "headers"] } lib-humus = { version="0.2", features=["axum-view+cookie"], git="https://codeberg.org/slatian/lib-humus.git" }
axum-extra = { version = "0.7", features = ["cookie"] }
axum-client-ip = "0.4" axum = { version = "0.7", features = ["macros"] }
axum-extra = { version = "0.9", features = ["cookie", "typed-header"] }
axum-client-ip = "0.5"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
governor = "0.5" governor = "0.6"
idna = "0.3" idna = "0.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
parking_lot = "0.12" parking_lot = "0.12"
regex = "1.7" regex = "1.10"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive","rc"] }
tokio = { version = "1", features = ["macros","signal"] } tokio = { version = "1", features = ["macros","signal"] }
tera = "1" tera = "1"
toml = "0.7" toml = "0.8"
tower = "0.4" tower = "0.4"
tower-http = { version = "0.4", features = ["fs"] } tower-http = { version = "0.5", features = ["fs"] }
trust-dns-proto = "0.22" trust-dns-proto = "0.23"
trust-dns-resolver = { version = "0.22", features = ["dns-over-rustls","dns-over-https","dns-over-quic"] } trust-dns-resolver = { version = "0.23", features = ["dns-over-rustls","dns-over-https","dns-over-quic"] }
maxminddb = "0.23" maxminddb = "0.23"
mime = "0.3"
http = "1.0"

View File

@ -72,7 +72,7 @@ This configuration option will not be exposed over the webinterface.
By default echoip-slatecave uses the system configuration for dns like most other programs. By default echoip-slatecave uses the system configuration for dns like most other programs.
In case this is undesired one can difable it by setting `enable_system_resolver` to false. In case this is undesired one can disable it by setting `enable_system_resolver` to false.
```toml ```toml
[dns] [dns]
@ -90,10 +90,6 @@ In case you want to use the system resolver and customize it.
`system_resolver_weight` `system_resolver_weight`
: Equivalent to the `weight` of a custom resolver, default: 1000 : Equivalent to the `weight` of a custom resolver, default: 1000
### `search`
This is for a work in progress feature that allows confiuring search domains for all custom dns resolvers.
### Custom resolvers ### Custom resolvers
It is possible to confgure custom resolvers in plce of or in addition to the default system resolver. It is possible to confgure custom resolvers in plce of or in addition to the default system resolver.

View File

@ -34,12 +34,33 @@ A less sane, but better for testing version can be found in [echoip_test.toml](e
Templates in the templates folder exist for every rich page that `echoip-slatecave` supports. Templates in the templates folder exist for every rich page that `echoip-slatecave` supports.
The code that rendeers them can be found in [src/templating_engine.rs](src/templating_engine.rs).
There is a configuration file for templates which by default is the `extra.toml` file in the template directory. Its content is exposed to the templates in the `extra` struct. There is a configuration file for templates which by default is the `extra.toml` file in the template directory. Its content is exposed to the templates in the `extra` struct.
The default templates should make use of everything exposed to the templating part, the `data.result` or `data` object is usually what you get when you ask for the json version. The default templates should make use of everything exposed to the templating part, the `data.result` or `data` object is usually what you get when you ask for the json version.
In addition to that the following fields are accessible from inside the template:
`view`
: The views name (the basename of the template file, i.e. `404` or `ip`)
`format`
: The format name (`html`, `text`, `json`)
`mimetype`
: The resulting mimetype (i.e. `text/plain; charset=utf-8`)
`http_status`
: The numeric HTTP Status-Code at the time of rendering the template.
`language`
: The language requested by the browser.
`dns_resolvers`
: A list of [Selectable](src/settings.rs) structs representing the available DNS-Resolvers.
`dns_resolver_id`
: The id of the currently selected DNS-Resolver
The templates are covered by the AGPL as well, please share them with your users if you modified them. The templates are covered by the AGPL as well, please share them with your users if you modified them.
### GeoLite2 database ### GeoLite2 database
@ -67,9 +88,9 @@ The default configuration is pretty liberal so that the average human probably w
## TODO ## TODO
* [ ] Investigate why search isn't working for global TLDs
* [ ] Add a way to configure just the dns server addresses and derive the port from the protocol. * [ ] Add a way to configure just the dns server addresses and derive the port from the protocol.
* [ ] Add an about page for the system resolver * [ ] Add an about page for the system resolver
* [ ] Expose DNS responses from the additional on the web interface
## License ## License

View File

@ -28,9 +28,6 @@ allow_reverse_lookup = true
# that end with one of these suffixes don't exist # that end with one of these suffixes don't exist
hidden_suffixes = [".com"] hidden_suffixes = [".com"]
# doesn't really work 🙁
search = ["org","net"]
[geoip] [geoip]
# Path to geoip databses # Path to geoip databses
# Currently only the mmdb format is supported # Currently only the mmdb format is supported
@ -116,7 +113,7 @@ tls_dns_name = "cloudflare-dns.com"
[dns.resolver.google] [dns.resolver.google]
display_name = "Google" display_name = "Google"
info_url = "https://www.cloudflare.com/dns/" info_url = "https://developers.google.com/speed/public-dns/docs/using"
aliases = ["goo","8888"] aliases = ["goo","8888"]
weight = 440 weight = 440

View File

@ -1,7 +1,7 @@
use serde::{Deserialize,Serialize}; use serde::{Deserialize,Serialize};
use trust_dns_resolver::config::Protocol; use trust_dns_resolver::config::Protocol;
use trust_dns_resolver::Name;
use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -11,13 +11,12 @@ pub struct DnsConfig {
pub allow_forward_lookup: bool, pub allow_forward_lookup: bool,
pub allow_reverse_lookup: bool, pub allow_reverse_lookup: bool,
pub hidden_suffixes: Vec<String>, pub hidden_suffixes: Vec<String>,
pub search: Vec<String>, pub resolver: HashMap<Arc<str>,DnsResolverConfig>,
pub resolver: HashMap<String,DnsResolverConfig>,
pub enable_system_resolver: bool, pub enable_system_resolver: bool,
pub system_resolver_name: String, pub system_resolver_name: Arc<str>,
pub system_resolver_weight: i32, pub system_resolver_weight: i32,
pub system_resolver_id: String, pub system_resolver_id: Arc<str>,
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
@ -32,22 +31,20 @@ pub enum DnsProtocol {
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct DnsResolverConfig { pub struct DnsResolverConfig {
pub display_name: String, pub display_name: Arc<str>,
#[serde(default)] #[serde(default)]
pub info_url: Option<String>, pub info_url: Option<Arc<str>>,
#[serde(default)] #[serde(default)]
pub aliases: Vec<String>, pub aliases: Vec<Arc<str>>,
#[serde(default="zero")] #[serde(default="zero")]
pub weight: i32, pub weight: i32,
pub servers: Vec<SocketAddr>, pub servers: Vec<SocketAddr>,
#[serde(default)]
pub search: Vec<String>,
pub protocol: DnsProtocol, pub protocol: DnsProtocol,
pub tls_dns_name: Option<String>, pub tls_dns_name: Option<Arc<str>>,
#[serde(skip_serializing)] //Don't leak our bind address to the outside #[serde(skip_serializing)] //Don't leak our bind address to the outside
pub bind_address: Option<SocketAddr>, pub bind_address: Option<SocketAddr>,
#[serde(default="default_true")] #[serde(default="default_true", alias="trust_nx_responses")]
pub trust_nx_responses: bool, pub trust_negative_responses: bool,
} }
fn zero() -> i32 { fn zero() -> i32 {
@ -65,12 +62,11 @@ impl Default for DnsConfig {
allow_reverse_lookup: false, allow_reverse_lookup: false,
hidden_suffixes: Vec::new(), hidden_suffixes: Vec::new(),
resolver: Default::default(), resolver: Default::default(),
search: Vec::new(),
enable_system_resolver: true, enable_system_resolver: true,
system_resolver_name: "System".to_string(), system_resolver_name: "System".into(),
system_resolver_weight: 1000, system_resolver_weight: 1000,
system_resolver_id: "system".to_string(), system_resolver_id: "system".into(),
} }
} }
} }
@ -89,30 +85,23 @@ impl Into<Protocol> for DnsProtocol {
impl DnsResolverConfig { impl DnsResolverConfig {
pub fn to_trust_resolver_config( pub fn to_trust_resolver_config(
&self, &self
additional_search: &Vec<String>,
) -> trust_dns_resolver::config::ResolverConfig { ) -> trust_dns_resolver::config::ResolverConfig {
let mut resolver = trust_dns_resolver::config::ResolverConfig::new(); let mut resolver = trust_dns_resolver::config::ResolverConfig::new();
for server in &self.servers { for server in &self.servers {
resolver.add_name_server(trust_dns_resolver::config::NameServerConfig{ resolver.add_name_server(trust_dns_resolver::config::NameServerConfig{
socket_addr: *server, socket_addr: *server,
protocol: self.protocol.clone().into(), protocol: self.protocol.clone().into(),
tls_dns_name: self.tls_dns_name.clone(), tls_dns_name: self.tls_dns_name.clone().map(|s| s.to_string()),
trust_nx_responses: self.trust_nx_responses, trust_negative_responses: self.trust_negative_responses,
tls_config: None, tls_config: None,
bind_addr: self.bind_address, bind_addr: self.bind_address,
}); });
} }
for search in &self.search { // Not configuring domain search here because searching
if let Ok(name) = Name::from_str_relaxed(search) { // on the resolver level is a bad idea unless we are
resolver.add_search(name); // taling about the system resolver which we
} // can't tell what to do (which is good!)
}
for search in additional_search {
if let Ok(name) = Name::from_str_relaxed(search) {
resolver.add_search(name);
}
}
return resolver; return resolver;
} }
} }

View File

@ -1,27 +1,28 @@
use axum::{ use axum::{
body::Body,
extract::{ extract::{
self, self,
Query, Query,
State, State,
Extension, Extension,
}, },
headers,
http::Request,
handler::Handler, handler::Handler,
http::Request,
middleware::{self, Next}, middleware::{self, Next},
response::Response, response::Response,
Router, Router,
routing::get, routing::get,
TypedHeader,
}; };
use axum_client_ip::SecureClientIp; use axum_client_ip::SecureClientIp;
use axum_extra::headers;
use axum_extra::TypedHeader;
use clap::Parser; use clap::Parser;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use serde::{Deserialize,Serialize}; use serde::{Deserialize,Serialize};
use tera::Tera;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use trust_dns_resolver::Name;
use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::TokioAsyncResolver;
use tokio::signal::unix::{ use tokio::signal::unix::{
@ -31,18 +32,21 @@ use tokio::signal::unix::{
use tokio::task; use tokio::task;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::Arc; use std::sync::Arc;
use lib_humus::TemplateEngineLoader;
use lib_humus::read_toml_from_file;
use lib_humus::HumusEngine;
mod config; mod config;
mod geoip; mod geoip;
mod idna;
mod ipinfo; mod ipinfo;
mod ratelimit; mod ratelimit;
mod settings; mod settings;
mod simple_dns; mod simple_dns;
mod templating_engine; mod view;
mod idna;
use crate::geoip::{ use crate::geoip::{
QueryAsn, QueryAsn,
@ -53,9 +57,11 @@ use crate::geoip::{
use crate::idna::IdnaName; use crate::idna::IdnaName;
use crate::simple_dns::DnsLookupResult; use crate::simple_dns::DnsLookupResult;
use crate::settings::*; use crate::settings::*;
use crate::templating_engine::View; use crate::view::View;
use crate::ipinfo::{AddressCast,AddressInfo,AddressScope}; use crate::ipinfo::{AddressCast,AddressInfo,AddressScope};
type TemplatingEngine = HumusEngine<View,QuerySettings,ResponseFormat>;
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct SettingsQuery { pub struct SettingsQuery {
format: Option<ResponseFormat>, format: Option<ResponseFormat>,
@ -75,26 +81,26 @@ pub struct IpResult {
asn: Option<AsnResult>, asn: Option<AsnResult>,
location: Option<LocationResult>, location: Option<LocationResult>,
ip_info: AddressInfo, ip_info: AddressInfo,
used_dns_resolver: Option<String>, used_dns_resolver: Option<Arc<str>>,
} }
// We need this one to hide the partial lookup field when irelevant // We need this one to hide the partial lookup field when irelevant
pub fn not(b: &bool) -> bool { !b } pub fn not(b: &bool) -> bool { !b }
#[derive(Serialize, Default, Clone)] #[derive(Serialize, Clone)]
pub struct DigResult { pub struct DigResult {
records: simple_dns::DnsLookupResult, records: simple_dns::DnsLookupResult,
#[serde(skip_serializing_if = "IdnaName::was_ascii")] #[serde(skip_serializing_if = "IdnaName::was_ascii")]
idn: IdnaName, idn: IdnaName,
#[serde(skip_serializing_if = "not")] #[serde(skip_serializing_if = "not")]
partial_lookup: bool, partial_lookup: bool,
used_dns_resolver: String, used_dns_resolver: Arc<str>,
} }
struct ServiceSharedState { struct ServiceSharedState {
templating_engine: templating_engine::Engine, templating_engine: TemplatingEngine,
dns_resolvers: HashMap<String,TokioAsyncResolver>, dns_resolvers: HashMap<Arc<str>,TokioAsyncResolver>,
dns_resolver_aliases: HashMap<String,String>, dns_resolver_aliases: HashMap<Arc<str>,Arc<str>>,
asn_db: geoip::MMDBCarrier, asn_db: geoip::MMDBCarrier,
location_db: geoip::MMDBCarrier, location_db: geoip::MMDBCarrier,
config: config::EchoIpServiceConfig, config: config::EchoIpServiceConfig,
@ -104,7 +110,7 @@ struct ServiceSharedState {
#[derive(Clone)] #[derive(Clone)]
struct DerivedConfiguration { struct DerivedConfiguration {
dns_resolver_selectables: Vec<Selectable>, dns_resolver_selectables: Vec<Selectable>,
default_resolver: String, default_resolver: Arc<str>,
} }
#[derive(Parser)] #[derive(Parser)]
@ -132,23 +138,6 @@ fn match_domain_hidden_list(domain: &String, hidden_list: &Vec<String>) -> bool
return false; return false;
} }
fn read_toml_from_file<T: for<'de> serde::Deserialize<'de>>(path: &String) -> Option<T> {
let text = match fs::read_to_string(path) {
Ok(t) => t,
Err(e) => {
println!("Error while reading file '{path}': {e}");
return None;
}
};
match toml::from_str(&text) {
Ok(t) => Some(t),
Err(e) => {
println!("Unable to parse file '{path}':\n{e}");
return None;
}
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Parse Command line arguments // Parse Command line arguments
@ -158,9 +147,11 @@ async fn main() {
let config: config::EchoIpServiceConfig = match cli_args.config { let config: config::EchoIpServiceConfig = match cli_args.config {
Some(config_path) => { Some(config_path) => {
match read_toml_from_file::<config::EchoIpServiceConfig>(&config_path) { match read_toml_from_file::<config::EchoIpServiceConfig>(&config_path) {
Some(c) => c, Ok(c) => c,
None => { Err(e) => {
println!("Could not read confuration file, exiting."); println!("Could not read confuration file!");
println!("{e}");
println!("Exiting ...");
::std::process::exit(1); ::std::process::exit(1);
} }
} }
@ -169,47 +160,25 @@ async fn main() {
}; };
// Initalize Tera templates // Initalize Tera templates
let mut template_base_dir = match cli_args.template_location { let template_loader = TemplateEngineLoader::new(
Some(template_base_dir) => template_base_dir, config.template.template_location.clone(),
None => (&config.template.template_location).to_owned(), config.template.extra_config.clone()
}; )
if !template_base_dir.ends_with("/") { .cli_template_location(cli_args.template_location)
template_base_dir = template_base_dir + "/"; .cli_extra_config_location(cli_args.extra_config);
}
let template_extra_config = match &cli_args.extra_config {
Some(path) => read_toml_from_file(path), let templating_engine = match template_loader.load_templates() {
None => match &config.template.extra_config { Ok(t) => t.into(),
Some(path) => read_toml_from_file(path),
None => {
println!("Trying to read default template configuration ...");
println!("(If this fails that may be ok, depending on your template)");
read_toml_from_file(&(template_base_dir.clone()+"extra.toml"))
},
},
};
let template_glob = template_base_dir.clone()+"*";
println!("Parsing Templates from '{}' ...", &template_glob);
let res = Tera::new((template_glob).as_str());
let tera = match res {
Ok(t) => t,
Err(e) => { Err(e) => {
println!("Template parsing error(s): {}", e); println!("{e}");
::std::process::exit(1); ::std::process::exit(1);
} }
}; };
let templating_engine = templating_engine::Engine{
tera: tera,
template_config: template_extra_config,
};
// Static file directory // Static file directory
let static_file_directory = cli_args.static_location.unwrap_or( let static_file_directory = template_loader.base_dir()+"/static";
config.server.static_location.clone().unwrap_or(
template_base_dir+"/static"
)
);
println!("Static files will be served from: {static_file_directory}"); println!("Static files will be served from: {static_file_directory}");
@ -233,8 +202,8 @@ async fn main() {
println!("Initalizing dns resolvers ..."); println!("Initalizing dns resolvers ...");
let mut dns_resolver_selectables = Vec::<Selectable>::new(); let mut dns_resolver_selectables = Vec::<Selectable>::new();
let mut dns_resolver_map: HashMap<String,TokioAsyncResolver> = HashMap::new(); let mut dns_resolver_map: HashMap<Arc<str>,TokioAsyncResolver> = HashMap::new();
let mut dns_resolver_aliases: HashMap<String,String> = HashMap::new(); let mut dns_resolver_aliases: HashMap<Arc<str>,Arc<str>> = HashMap::new();
if config.dns.enable_system_resolver { if config.dns.enable_system_resolver {
println!("Initalizing System resolver ..."); println!("Initalizing System resolver ...");
@ -258,9 +227,9 @@ async fn main() {
for (key, resolver_config) in &config.dns.resolver { for (key, resolver_config) in &config.dns.resolver {
println!("Initalizing {} resolver ...", key); println!("Initalizing {} resolver ...", key);
let resolver = TokioAsyncResolver::tokio( let resolver = TokioAsyncResolver::tokio(
resolver_config.to_trust_resolver_config(&config.dns.search), resolver_config.to_trust_resolver_config(),
Default::default() Default::default()
).unwrap(); );
dns_resolver_map.insert(key.clone(), resolver); dns_resolver_map.insert(key.clone(), resolver);
dns_resolver_selectables.push(Selectable { dns_resolver_selectables.push(Selectable {
id: key.clone(), id: key.clone(),
@ -289,7 +258,7 @@ async fn main() {
dns_resolver_selectables.sort_by(|a,b| b.weight.cmp(&a.weight)); dns_resolver_selectables.sort_by(|a,b| b.weight.cmp(&a.weight));
let default_resolver = dns_resolver_selectables.get(0) let default_resolver = dns_resolver_selectables.get(0)
.map(|s| s.id.clone() ) .map(|s| s.id.clone() )
.unwrap_or("none".to_string()); .unwrap_or("none".into());
let derived_config = DerivedConfiguration { let derived_config = DerivedConfiguration {
dns_resolver_selectables: dns_resolver_selectables, dns_resolver_selectables: dns_resolver_selectables,
default_resolver: default_resolver, default_resolver: default_resolver,
@ -343,30 +312,30 @@ async fn main() {
println!("Starting Server on {} ...",listen_on); println!("Starting Server on {} ...",listen_on);
axum::Server::bind(&listen_on) let listener = tokio::net::TcpListener::bind(&listen_on).await.unwrap();
.serve(app.into_make_service_with_connect_info::<std::net::SocketAddr>()) axum::serve(listener, app.into_make_service_with_connect_info::<std::net::SocketAddr>())
.await .await
.unwrap(); .unwrap();
} }
async fn settings_query_middleware<B>( async fn settings_query_middleware(
Query(query): Query<SettingsQuery>, Query(query): Query<SettingsQuery>,
Extension(config): Extension<config::EchoIpServiceConfig>, Extension(config): Extension<config::EchoIpServiceConfig>,
Extension(derived_config): Extension<DerivedConfiguration>, Extension(derived_config): Extension<DerivedConfiguration>,
cookie_header: Option<TypedHeader<headers::Cookie>>, cookie_header: Option<TypedHeader<headers::Cookie>>,
user_agent_header: Option<TypedHeader<headers::UserAgent>>, user_agent_header: Option<TypedHeader<headers::UserAgent>>,
mut req: Request<B>, mut req: Request<Body>,
next: Next<B> next: Next
) -> Response { ) -> Response {
let mut format = query.format; let mut format = query.format;
let mut dns_resolver_id = derived_config.default_resolver; let mut dns_resolver_id = derived_config.default_resolver;
if let Some(resolver_id) = query.dns { if let Some(resolver_id) = query.dns {
dns_resolver_id = resolver_id; dns_resolver_id = resolver_id.into();
} else if let Some(cookie_header) = cookie_header { } else if let Some(cookie_header) = cookie_header {
if let Some(resolver_id) = cookie_header.0.get("dns_resolver") { if let Some(resolver_id) = cookie_header.0.get("dns_resolver") {
dns_resolver_id = resolver_id.to_string(); dns_resolver_id = resolver_id.into();
} }
} }
@ -376,7 +345,7 @@ async fn settings_query_middleware<B>(
let ua = user_agent.as_str(); let ua = user_agent.as_str();
for tua in config.template.text_user_agents { for tua in config.template.text_user_agents {
if ua.starts_with(&tua) { if ua.starts_with(&tua) {
format = Some(ResponseFormat::TextPlain); format = Some(ResponseFormat::Text);
break; break;
} }
} }
@ -384,7 +353,7 @@ async fn settings_query_middleware<B>(
} }
// Add the request settings extension // Add the request settings extension
req.extensions_mut().insert(QuerySettings{ req.extensions_mut().insert(QuerySettings{
format: format.unwrap_or(ResponseFormat::TextHtml), format: format.unwrap_or(ResponseFormat::Html),
lang: query.lang.unwrap_or("en".to_string()), lang: query.lang.unwrap_or("en".to_string()),
available_dns_resolvers: derived_config.dns_resolver_selectables, available_dns_resolvers: derived_config.dns_resolver_selectables,
dns_resolver_id: dns_resolver_id, dns_resolver_id: dns_resolver_id,
@ -400,8 +369,8 @@ async fn not_found_handler(
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::NotFound, View::NotFound,
).await )
} }
async fn hello_world_handler( async fn hello_world_handler(
@ -412,11 +381,11 @@ async fn hello_world_handler(
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::Message{ View::Message{
title: "Hey There!".to_string(), title: "Hey There!".to_string(),
message: "You,You are an awesome Creature!".to_string() message: "You are an awesome Creature!".to_string()
}, },
).await )
} }
@ -447,7 +416,7 @@ async fn handle_default_route(
} }
} }
let result = get_ip_result(&address, &settings.lang, &"default".to_string(), &state).await; let result = get_ip_result(&address, &settings.lang, &"default".into(), &state).await;
let user_agent: Option<String> = match user_agent_header { let user_agent: Option<String> = match user_agent_header {
Some(TypedHeader(user_agent)) => Some(user_agent.to_string()), Some(TypedHeader(user_agent)) => Some(user_agent.to_string()),
@ -456,11 +425,11 @@ async fn handle_default_route(
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::Index{ View::Index{
result: result, result: result,
user_agent: user_agent, user_agent: user_agent,
} }
).await )
} }
@ -486,17 +455,17 @@ async fn handle_search_request(
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
return state.templating_engine.render_view( return state.templating_engine.render_view(
&settings, &settings,
&View::Asn{asn: asn}, View::Asn{asn: asn},
).await )
} }
} }
if let Some(via_cap) = VIA_REGEX.captures(&search_query) { if let Some(via_cap) = VIA_REGEX.captures(&search_query) {
if let Some(via) = via_cap.get(1).map(|c| c.as_str().to_string()) { if let Some(via) = via_cap.get(1) {
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
if state.dns_resolvers.contains_key(&via) { if state.dns_resolvers.contains_key(via.as_str()) {
settings.dns_resolver_id = via; settings.dns_resolver_id = via.as_str().into();
} else if let Some(alias) = state.dns_resolver_aliases.get(&via) { } else if let Some(alias) = state.dns_resolver_aliases.get(via.as_str()) {
settings.dns_resolver_id = alias.clone(); settings.dns_resolver_id = alias.clone();
} }
} }
@ -525,8 +494,8 @@ async fn handle_dns_resolver_route(
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::DnsResolverList, View::DnsResolverList,
).await )
} }
@ -536,16 +505,16 @@ async fn handle_dns_resolver_route_with_path(
extract::Path(query): extract::Path<String>, extract::Path(query): extract::Path<String>,
) -> Response { ) -> Response {
let state = Arc::clone(&arc_state); let state = Arc::clone(&arc_state);
if let Some(resolver) = state.config.dns.resolver.get(&query) { if let Some(resolver) = state.config.dns.resolver.get(query.as_str()) {
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::DnsResolver{ config: resolver.clone() }, View::DnsResolver{ config: resolver.clone() },
).await )
} else { } else {
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::NotFound, View::NotFound,
).await )
} }
} }
@ -576,14 +545,14 @@ async fn handle_ip_request(
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::Ip{result: result} View::Ip{result: result}
).await )
} }
async fn get_ip_result( async fn get_ip_result(
address: &IpAddr, address: &IpAddr,
lang: &String, lang: &String,
dns_resolver_name: &String, dns_resolver_name: &Arc<str>,
state: &ServiceSharedState, state: &ServiceSharedState,
) -> IpResult { ) -> IpResult {
@ -604,7 +573,7 @@ async fn get_ip_result(
// do reverse lookup // do reverse lookup
let mut hostname: Option<String> = None; let mut hostname: Option<String> = None;
let mut used_dns_resolver: Option<String> = None; let mut used_dns_resolver: Option<Arc<str>> = None;
if state.config.dns.allow_reverse_lookup { if state.config.dns.allow_reverse_lookup {
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
hostname = simple_dns::reverse_lookup(&dns_resolver, &address).await; hostname = simple_dns::reverse_lookup(&dns_resolver, &address).await;
@ -665,39 +634,40 @@ async fn handle_dig_request(
state.templating_engine.render_view( state.templating_engine.render_view(
&settings, &settings,
&View::Dig{ query: dig_query, result: dig_result} View::Dig{ query: dig_query, result: dig_result}
).await )
} }
async fn get_dig_result( async fn get_dig_result(
dig_query: &String, dig_query: &String,
dns_resolver_name: &String, dns_resolver_name: &Arc<str>,
state: &ServiceSharedState, state: &ServiceSharedState,
do_full_lookup: bool, do_full_lookup: bool,
) -> DigResult { ) -> DigResult {
let name = &dig_query.trim().trim_end_matches(".").to_string(); let name = &dig_query.trim().trim_end_matches(".").to_string();
let idna_name = IdnaName::from_string(&name); let idna_name = IdnaName::from_string(&name);
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { if let Some(dns_resolver) = state.dns_resolvers.get(dns_resolver_name) {
if let Ok(domain_name) = Name::from_str_relaxed(name.to_owned()+".") {
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) { if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
// Try to hide the fact that we didn't do dns resolution at all // Try to hide the fact that we didn't do dns resolution at all
// We resolve example.org as basic avoidance of timing sidechannels. // We resolve example.org as basic avoidance of timing sidechannels.
// WARNING: this timing sidechannel avoidance is very crude. // WARNING: this timing sidechannel avoidance is very crude.
simple_dns::lookup( simple_dns::lookup(
&dns_resolver, &dns_resolver,
&("example.org.".to_string()), &Name::from_ascii("example.org.").expect("Static Dummy Name"),
do_full_lookup).await; do_full_lookup).await;
DigResult { return DigResult {
records: DnsLookupResult{ nxdomain: true , ..Default::default() }, records: DnsLookupResult{ nxdomain: true , ..Default::default() },
idn: idna_name, idn: idna_name,
partial_lookup: !do_full_lookup, partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(), used_dns_resolver: dns_resolver_name.clone(),
} }
} else { } else {
DigResult { return DigResult {
records: simple_dns::lookup( records: simple_dns::lookup(
&dns_resolver, &dns_resolver,
&(idna_name.idn.clone().unwrap_or(name.to_owned())+"."), &domain_name,
do_full_lookup).await, do_full_lookup).await,
idn: idna_name, idn: idna_name,
partial_lookup: !do_full_lookup, partial_lookup: !do_full_lookup,
@ -705,6 +675,27 @@ async fn get_dig_result(
} }
} }
} else { } else {
return Default::default(); // Invalid domain name
return DigResult {
records: DnsLookupResult{
invalid_name: true,
.. Default::default()
},
idn: idna_name,
partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(),
}
}
} else {
// Unknown resolver name
return DigResult {
records: DnsLookupResult{
unkown_resolver: true,
.. Default::default()
},
idn: idna_name,
partial_lookup: !do_full_lookup,
used_dns_resolver: "unkown_resolver".into(),
}
} }
} }

View File

@ -1,5 +1,6 @@
use axum_client_ip::SecureClientIp; use axum_client_ip::SecureClientIp;
use axum::{ use axum::{
body::Body,
extract::Extension, extract::Extension,
http::{ http::{
Request, Request,
@ -40,11 +41,11 @@ pub fn build_rate_limiting_state(
Extension(arc_limiter) Extension(arc_limiter)
} }
pub async fn rate_limit_middleware<B>( pub async fn rate_limit_middleware(
SecureClientIp(address): SecureClientIp, SecureClientIp(address): SecureClientIp,
Extension(arc_limiter): Extension<Arc<SimpleRateLimiter<IpAddr>>>, Extension(arc_limiter): Extension<Arc<SimpleRateLimiter<IpAddr>>>,
req: Request<B>, req: Request<Body>,
next: Next<B> next: Next
) -> Response { ) -> Response {
let limiter = Arc::clone(&arc_limiter); let limiter = Arc::clone(&arc_limiter);

View File

@ -1,36 +1,13 @@
use serde::{Deserialize,Serialize}; use serde::{Deserialize,Serialize};
use lib_humus::HtmlTextJsonFormat;
use lib_humus::HumusQuerySettings;
use std::sync::Arc;
/* Response format */ /* Response format */
#[derive(Deserialize, Serialize, Clone, Copy)] pub type ResponseFormat = HtmlTextJsonFormat;
pub enum ResponseFormat {
#[serde(rename="text/plain", alias="text")]
TextPlain,
#[serde(rename="text/html", alias="html")]
TextHtml,
#[serde(rename="application/json", alias="json")]
ApplicationJson,
}
impl ToString for ResponseFormat {
fn to_string(&self) -> String {
match self {
ResponseFormat::TextPlain => "text/plain",
ResponseFormat::TextHtml => "text/html",
ResponseFormat::ApplicationJson => "application/json",
}.to_string()
}
}
impl ResponseFormat {
pub fn to_file_extension(&self) -> String {
match self {
ResponseFormat::TextPlain => ".txt",
ResponseFormat::TextHtml => ".html",
ResponseFormat::ApplicationJson => ".json",
}.to_string()
}
}
/* Query and Template Settings */ /* Query and Template Settings */
@ -39,13 +16,26 @@ pub struct QuerySettings {
pub format: ResponseFormat, pub format: ResponseFormat,
pub lang: String, pub lang: String,
pub available_dns_resolvers: Vec<Selectable>, pub available_dns_resolvers: Vec<Selectable>,
pub dns_resolver_id: String, pub dns_resolver_id: Arc<str>,
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct Selectable { pub struct Selectable {
pub id: String, pub id: Arc<str>,
pub name: String, pub name: Arc<str>,
pub weight: i32, pub weight: i32,
} }
impl HumusQuerySettings<ResponseFormat> for QuerySettings {
fn initalize_template_context(&self, context: &mut tera::Context) {
context.insert("language", &self.lang);
context.insert("dns_resolvers", &self.available_dns_resolvers);
context.insert("dns_resolver_id", &self.dns_resolver_id);
}
fn get_format(&self) -> ResponseFormat {
self.format.clone()
}
}

View File

@ -15,6 +15,7 @@ use trust_dns_resolver::{
error::ResolveError, error::ResolveError,
error::ResolveErrorKind, error::ResolveErrorKind,
lookup::Lookup, lookup::Lookup,
Name,
TokioAsyncResolver, TokioAsyncResolver,
}; };
@ -41,6 +42,8 @@ pub struct DnsLookupResult {
pub dns_error: bool, pub dns_error: bool,
pub nxdomain: bool, pub nxdomain: bool,
pub timeout: bool, pub timeout: bool,
pub invalid_name: bool,
pub unkown_resolver: bool,
} }
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)] #[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]
@ -118,9 +121,9 @@ pub fn set_default_if_none<T>(opt_vec: &mut Option<Vec<T>>) {
pub fn add_record_to_lookup_result(result: &mut DnsLookupResult, record: &RData){ pub fn add_record_to_lookup_result(result: &mut DnsLookupResult, record: &RData){
match record { match record {
RData::AAAA(address) => opush(&mut result.aaaa, std::net::IpAddr::V6(*address)), RData::AAAA(address) => opush(&mut result.aaaa, std::net::IpAddr::V6(address.0)),
RData::ANAME(aname) => opush(&mut result.aname, aname.to_string()), RData::ANAME(aname) => opush(&mut result.aname, aname.to_string()),
RData::A(address) => opush(&mut result.a, std::net::IpAddr::V4(*address)), RData::A(address) => opush(&mut result.a, std::net::IpAddr::V4(address.0)),
RData::CAA(caa) => opush(&mut result.caa, caa.to_string()), RData::CAA(caa) => opush(&mut result.caa, caa.to_string()),
RData::CNAME(cname) => opush(&mut result.cname, cname.to_string()), RData::CNAME(cname) => opush(&mut result.cname, cname.to_string()),
RData::MX(mx) => opush(&mut result.mx, MxRecord{ RData::MX(mx) => opush(&mut result.mx, MxRecord{
@ -171,8 +174,14 @@ pub fn integrate_lookup_result(dig_result: &mut DnsLookupResult, lookup_result:
RecordType::TXT => set_default_if_none(&mut dig_result.txt), RecordType::TXT => set_default_if_none(&mut dig_result.txt),
_ => { /* This should not happen */ }, _ => { /* This should not happen */ },
}; };
for record in lookup.iter() { let name = lookup.query().name();
add_record_to_lookup_result(dig_result, record); for record in lookup.record_iter() {
if name == record.name() {
if let Some(data) = record.data() {
add_record_to_lookup_result(dig_result, data);
}
}
//TODO: handle additional responses
} }
}, },
Err(e) => { Err(e) => {
@ -210,7 +219,7 @@ pub fn integrate_lookup_result(dig_result: &mut DnsLookupResult, lookup_result:
// records will be fetched. // records will be fetched.
pub async fn lookup( pub async fn lookup(
resolver: &TokioAsyncResolver, resolver: &TokioAsyncResolver,
name: &String, name: &Name,
do_full_lookup: bool, do_full_lookup: bool,
) -> DnsLookupResult { ) -> DnsLookupResult {
let ( let (
@ -219,10 +228,10 @@ pub async fn lookup(
cname_lookup_res, cname_lookup_res,
aname_lookup_res aname_lookup_res
) = join!( ) = join!(
resolver.lookup(name, RecordType::A), resolver.lookup(name.clone(), RecordType::A),
resolver.lookup(name, RecordType::AAAA), resolver.lookup(name.clone(), RecordType::AAAA),
resolver.lookup(name, RecordType::CNAME), resolver.lookup(name.clone(), RecordType::CNAME),
resolver.lookup(name, RecordType::ANAME), resolver.lookup(name.clone(), RecordType::ANAME),
); );
// initlize an empty lookup result // initlize an empty lookup result
@ -243,12 +252,12 @@ pub async fn lookup(
srv_lookup_res, srv_lookup_res,
txt_lookup_res txt_lookup_res
) = join!( ) = join!(
resolver.lookup(name, RecordType::MX), resolver.lookup(name.clone(), RecordType::MX),
resolver.lookup(name, RecordType::NS), resolver.lookup(name.clone(), RecordType::NS),
resolver.lookup(name, RecordType::SOA), resolver.lookup(name.clone(), RecordType::SOA),
resolver.lookup(name, RecordType::CAA), resolver.lookup(name.clone(), RecordType::CAA),
resolver.lookup(name, RecordType::SRV), resolver.lookup(name.clone(), RecordType::SRV),
resolver.lookup(name, RecordType::TXT), resolver.lookup(name.clone(), RecordType::TXT),
); );
integrate_lookup_result(&mut dig_result, mx_lookup_res); integrate_lookup_result(&mut dig_result, mx_lookup_res);

View File

@ -1,135 +0,0 @@
/*
* This is the echoip-slatecave templating engine.
* It wraps around tera in is specialized for echoip-slatecave.
*/
use axum::{
headers::HeaderValue,
http::StatusCode,
http::header::SET_COOKIE,
response::Html,
response::IntoResponse,
response::Response,
response::Json,
};
use axum_extra::extract::cookie::Cookie;
use axum_extra::extract::cookie;
use tera::Tera;
use toml::Table;
use crate::DigResult;
use crate::IpResult;
use crate::config::DnsResolverConfig;
use crate::settings::*;
/* The echoip view */
#[derive(serde::Serialize, Clone)]
#[serde(untagged)]
pub enum View {
Asn { asn: u32 },
Dig { query: String, result: DigResult },
DnsResolver{ config: DnsResolverConfig },
DnsResolverList,
Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult },
Message{ title: String, message: String },
#[serde(rename="404")]
NotFound,
}
impl View {
pub fn template_name(&self) -> String {
match self {
View::Asn{..} => "asn",
View::Dig{..} => "dig",
View::DnsResolver{..} => "dns_resolver",
View::DnsResolverList => "dns_resolver_list",
View::Index{..} => "index",
View::Ip{..} => "ip",
View::Message{..} => "message",
View::NotFound => "404",
}.to_string()
}
}
/* The engine itself */
#[derive(Clone)]
pub struct Engine {
pub tera: Tera,
pub template_config: Option<Table>,
}
impl Engine {
pub async fn render_view(
&self,
settings: &QuerySettings,
view: &View,
) -> Response {
let mut response = match settings.format {
ResponseFormat::TextHtml | ResponseFormat::TextPlain => {
let template_name = view.template_name();
let mut context = tera::Context::new();
context.insert("view", &template_name);
//intented for shared macros
context.insert("format", &settings.format.to_string());
context.insert("language", &settings.lang);
context.insert("dns_resolvers", &settings.available_dns_resolvers);
context.insert("dns_resolver_id", &settings.dns_resolver_id);
context.insert("data", &view);
context.insert("extra", &self.template_config);
match self.tera.render(&(template_name+&settings.format.to_file_extension()), &context) {
Ok(text) =>
match settings.format {
ResponseFormat::TextHtml => Html(text).into_response(),
_ => text.into_response(),
}
Err(e) => {
println!("There was an error while rendering template {}: {e:?}", view.template_name());
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Template error in {}, contact owner or see logs.\n", view.template_name())
).into_response()
}
}
}
//TODO: Plain Text should have its own matcher
ResponseFormat::ApplicationJson => {
match view {
View::Dig{result, ..} => {
Json(result).into_response()
},
View::Index{result, ..} | View::Ip{result, ..} => {
Json(result).into_response()
},
View::DnsResolverList => {
Json(settings.available_dns_resolvers.clone()).into_response()
},
View::DnsResolver{ config } => {
Json(config).into_response()
}
_ => Json(view).into_response(),
}
}
};
match view {
View::NotFound => *response.status_mut() = StatusCode::NOT_FOUND,
_ => {},
}
let cookie = Cookie::build("dns_resolver",settings.dns_resolver_id.clone())
.path("/")
.same_site(cookie::SameSite::Strict)
.finish();
if let Ok(header_value) = HeaderValue::from_str(&cookie.to_string()) {
response.headers_mut().append(
SET_COOKIE,
header_value,
);
}
response
}
}

80
src/view.rs Normal file
View File

@ -0,0 +1,80 @@
use axum::http::status::StatusCode;
use axum::Json;
use axum::response::IntoResponse;
use axum::response::Response;
use axum_extra::extract::cookie::Cookie;
use axum_extra::extract::cookie;
use lib_humus::HumusView;
use crate::DigResult;
use crate::IpResult;
use crate::config::DnsResolverConfig;
use crate::settings::QuerySettings;
use crate::settings::ResponseFormat;
#[derive(serde::Serialize, Clone)]
#[serde(untagged)]
pub enum View {
Asn { asn: u32 },
Dig { query: String, result: DigResult },
DnsResolver{ config: DnsResolverConfig },
DnsResolverList,
Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult },
Message{ title: String, message: String },
#[serde(rename="404")]
NotFound,
}
impl HumusView<QuerySettings, ResponseFormat> for View {
fn get_template_name(&self) -> String {
match self {
View::Asn{..} => "asn",
View::Dig{..} => "dig",
View::DnsResolver{..} => "dns_resolver",
View::DnsResolverList => "dns_resolver_list",
View::Index{..} => "index",
View::Ip{..} => "ip",
View::Message{..} => "message",
View::NotFound => "404",
}.to_string()
}
fn get_status_code(&self, _: &QuerySettings) -> StatusCode {
match self {
Self::NotFound => StatusCode::NOT_FOUND,
_ => StatusCode::OK,
}
}
fn get_cookie_header(&self, settings: &QuerySettings) -> Option<String> {
Some(
Cookie::build(Cookie::new("dns_resolver",settings.dns_resolver_id.to_string()))
.path("/")
.same_site(cookie::SameSite::Strict)
.build()
.to_string()
)
}
fn get_api_response(self, settings: &QuerySettings) -> Response {
match self {
Self::Dig{result, ..} => {
Json(result).into_response()
},
Self::Index{result, ..} | Self::Ip{result, ..} => {
Json(result).into_response()
},
Self::DnsResolverList => {
Json(settings.available_dns_resolvers.clone()).into_response()
},
Self::DnsResolver{ config } => {
Json(config).into_response()
}
_ => Json(self).into_response(),
}
}
}

View File

@ -26,8 +26,16 @@
<section> <section>
<h2>DNS Records</h2> <h2>DNS Records</h2>
{% if r.nxdomain %} {% set show_nonpresent = true %}
<p class="error box">Our DNS-Server claims that this domain doesn't exist, there shouldn't be any results.</p> {% if r.unkown_resolver %}
<p class="error box">The resolver you chose is not one of the available ones, if you can reproduce this error by just using the UI <a href="https://codeberg.org/slatian/service.echoip-slatecave/issues/new">please report it</a>.</p>
{% set show_nonpresent = false %}
{% elif r.invalid_name %}
<p class="error box">This domain name does not conform to <a href="https://www.rfc-editor.org/info/std3">the dns specification (std3)</a> rules and was therefore not resolved.</p>
{% set show_nonpresent = false %}
{% elif r.nxdomain %}
<p class="error box">The DNS-Server claims that this domain doesn't exist, there shouldn't be any results.</p>
{% set show_nonpresent = false %}
{% elif r.timeout %} {% elif r.timeout %}
<p class="error box">There was at least one timeout error while resolving this domain, the results below are incomplete.</p> <p class="error box">There was at least one timeout error while resolving this domain, the results below are incomplete.</p>
{% elif r.other_error %} {% elif r.other_error %}
@ -66,7 +74,7 @@
<li>{{ helper::ip(extra=extra, ip=address) }}</li> <li>{{ helper::ip(extra=extra, ip=address) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p>No <code>A</code> (IPv4) Records.</p> <p>No <code>A</code> (IPv4) Records.</p>
{% endif %} {% endif %}
@ -77,7 +85,7 @@
<li>{{ helper::ip(extra=extra, ip=address) }}</li> <li>{{ helper::ip(extra=extra, ip=address) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p>No <code>AAAA</code> (IPv6) Records.</p> <p>No <code>AAAA</code> (IPv6) Records.</p>
{% endif %} {% endif %}
@ -90,7 +98,7 @@
<li>{{ helper::dig(extra=extra, name=mx.exchange, fqdn=true, prefix=mx.preference) }}</li> <li>{{ helper::dig(extra=extra, name=mx.exchange, fqdn=true, prefix=mx.preference) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="mx">No <code>MX</code> (Mail Exchange) records.</p> <p id="mx">No <code>MX</code> (Mail Exchange) records.</p>
{% endif %} {% endif %}
@ -116,7 +124,7 @@
</dl></li> </dl></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="soa">No <code>SOA</code> records.</p> <p id="soa">No <code>SOA</code> records.</p>
{% endif %} {% endif %}
@ -129,7 +137,7 @@
<li>{{ helper::dig(extra=extra, name=ns) }}</li> <li>{{ helper::dig(extra=extra, name=ns) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="ns">No <code>NS</code> (Name Server) records.</p> <p id="ns">No <code>NS</code> (Name Server) records.</p>
{% endif %} {% endif %}
@ -141,7 +149,7 @@
<li><code>{{caa}}</code></li> <li><code>{{caa}}</code></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="caa">No <code>CAA</code> (<a target="_blank" href="https://de.wikipedia.org/wiki/DNS_Certification_Authority_Authorization">Certification Authority Authorization</a>) records.</p> <p id="caa">No <code>CAA</code> (<a target="_blank" href="https://de.wikipedia.org/wiki/DNS_Certification_Authority_Authorization">Certification Authority Authorization</a>) records.</p>
{% endif %} {% endif %}
@ -152,7 +160,7 @@
<li><code>{{txt}}</code></li> <li><code>{{txt}}</code></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="txt">No <code>TXT</code> records.</p> <p id="txt">No <code>TXT</code> records.</p>
{% endif %} {% endif %}
@ -172,7 +180,7 @@
</dl></li> </dl></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% elif not r.nxdomain %} {% elif show_nonpresent %}
<p id="srv">No <code>SRV</code> records.</p> <p id="srv">No <code>SRV</code> records.</p>
<p><code>SRV</code> or Service records usually live on their own subdomains like {{ helper::dig(extra=extra, name="_xmpp-client._tcp."~data.query) }}. <p><code>SRV</code> or Service records usually live on their own subdomains like {{ helper::dig(extra=extra, name="_xmpp-client._tcp."~data.query) }}.
{% endif %} {% endif %}

View File

@ -2,7 +2,8 @@
{% block path %}dig/{{ data.query | urlencode_strict }}{% endblock %} {% block path %}dig/{{ data.query | urlencode_strict }}{% endblock %}
{% block content -%} {% set r = data.result.records %}
{%- block content -%}
# dig {{data.query}} via {{ data.result.used_dns_resolver }} # dig {{data.query}} via {{ data.result.used_dns_resolver }}
{% if data.result.idn -%} {% if data.result.idn -%}
@ -25,7 +26,16 @@ Your IDN would decode to
{% set r = data.result.records -%} {% set r = data.result.records -%}
## DNS Records ## DNS Records
{% if r.nxdomain %} {% if r.unkown_resolver %}
{%- set show_nonpresent = false %}
The resolver you chose is not one of the available ones.
=> {{ extra.base_url }}/dns_resolver
{% elif r.invalid_name %}
{%- set show_nonpresent = false %}
This domain name does not conform to the dns specification (std3) rules and was therefore not resolved.
=> https://www.rfc-editor.org/info/std3
{% elif r.nxdomain %}
{%- set show_nonpresent = false %}
Our DNS-Server claims that this domain doesn't exist, there shouldn't be any results. Our DNS-Server claims that this domain doesn't exist, there shouldn't be any results.
{%- elif r.timeout -%} {%- elif r.timeout -%}
There was at least one timeout error while resolving this domain, the results below are incomplete. There was at least one timeout error while resolving this domain, the results below are incomplete.
@ -61,7 +71,7 @@ A (IPv4) records:
{% for address in r.a -%} {% for address in r.a -%}
* {{ address }} * {{ address }}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No A (IPv4) Records. No A (IPv4) Records.
{% endif -%} {% endif -%}
@ -70,7 +80,7 @@ AAAA (IPv6) records:
{% for address in r.aaaa -%} {% for address in r.aaaa -%}
* {{ address }} * {{ address }}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No AAAA (IPv6) Records. No AAAA (IPv6) Records.
{% endif -%} {% endif -%}
@ -81,7 +91,7 @@ MX (Mail Exchange) records:
{% for mx in r.mx | sort(attribute="preference") | reverse -%} {% for mx in r.mx | sort(attribute="preference") | reverse -%}
* {{ mx.preference }} {{ mx.exchange }} * {{ mx.preference }} {{ mx.exchange }}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No MX (Mail Exchange) records. No MX (Mail Exchange) records.
{% endif %} {% endif %}
@ -96,7 +106,7 @@ SOA (Source Of Authority) records:
* expire: {{soa.expire / 3600 | round(precision=2)}}h * expire: {{soa.expire / 3600 | round(precision=2)}}h
* minimum: {{soa.minimum / 60 | round(precision=2)}}m TTL * minimum: {{soa.minimum / 60 | round(precision=2)}}m TTL
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No SOA (Source Of Authority) records. No SOA (Source Of Authority) records.
{% endif %} {% endif %}
@ -105,7 +115,7 @@ NS (Name Server) records:
{% for ns in r.ns -%} {% for ns in r.ns -%}
* {{ns}} * {{ns}}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No NS (Name Server) records. No NS (Name Server) records.
{% endif %} {% endif %}
@ -114,7 +124,7 @@ CAA (Certification Authority Authorization) records:
{% for caa in r.caa -%} {% for caa in r.caa -%}
* {{caa}} * {{caa}}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No CAA (Certification Authority Authorization) records. No CAA (Certification Authority Authorization) records.
{% endif %} {% endif %}
@ -123,7 +133,7 @@ TXT records:
{% for txt in r.txt -%} {% for txt in r.txt -%}
* {{txt}} * {{txt}}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No TXT records. No TXT records.
{% endif %} {% endif %}
@ -135,7 +145,7 @@ SRV records:
* Port: {{srv.port}} * Port: {{srv.port}}
* Target: {{srv.target}} * Target: {{srv.target}}
{% endfor %} {% endfor %}
{%- elif not r.nxdomain %} {%- elif show_nonpresent %}
No SRV records. No SRV records.
SRV or Service records usually live on their own subdomains like {{ "_xmpp-client._tcp."~data.query }}. SRV or Service records usually live on their own subdomains like {{ "_xmpp-client._tcp."~data.query }}.

View File

@ -31,12 +31,6 @@
<dd>{{ helper::dig(extra=extra, name=c.tls_dns_name) }}</dd> <dd>{{ helper::dig(extra=extra, name=c.tls_dns_name) }}</dd>
{%- endif %} {%- endif %}
{%- if c.search | length > 0 %}
<dt>Search</dt>
{%- for s in c.search %}
<dd>{{s}}</dd>
{%- endfor %}
{%- endif %}
</dl> </dl>
{%- if c.info_url %} {%- if c.info_url %}
<p class="button-paragraph"><a href="{{c.info_url}}">More about the {{c.display_name}} DNS Server <small>(external link)</small></a></p> <p class="button-paragraph"><a href="{{c.info_url}}">More about the {{c.display_name}} DNS Server <small>(external link)</small></a></p>

View File

@ -16,14 +16,6 @@ Protocol: {{ c.protocol }}
{%-if c.tls_dns_name %} {%-if c.tls_dns_name %}
DNS Name: {{ c.tls_dns_name }} DNS Name: {{ c.tls_dns_name }}
{%- endif %} {%- endif %}
{%- if c.search | length == 1 %}
Search: {{ c.search | first }}
{%- elif c.search | length > 1 %}
Search:
{%- for s in c.search %}
* {{s}}
{%- endfor %}
{%- endif %}
{%- if c.aliases | length == 1 %} {%- if c.aliases | length == 1 %}
Alias: {{ c.aliases | first }} Alias: {{ c.aliases | first }}
{%- elif c.aliases | length > 1 %} {%- elif c.aliases | length > 1 %}

View File

@ -1,6 +1,6 @@
{% macro place_dl(place, label="", iso_code_prefix="") -%} {% macro place_dl(place, label="", iso_code_prefix="") -%}
{%- if place -%} {%- if place -%}
{%- if format=="text/html" %} {%- if format=="html" %}
{% if label %}<dt>{{label}}</dt>{% endif %} {% if label %}<dt>{{label}}</dt>{% endif %}
<dd>{{place.name}} {% if place.iso_code%}({% if iso_code_prefix %}{{iso_code_prefix}}-{% endif %}{{place.iso_code}}){% endif %}</dd> <dd>{{place.name}} {% if place.iso_code%}({% if iso_code_prefix %}{{iso_code_prefix}}-{% endif %}{{place.iso_code}}){% endif %}</dd>
{% else -%} {% else -%}

View File

@ -16,23 +16,24 @@
{% macro domain_name_links(name) %} {% macro domain_name_links(name) %}
<p>Look up <code>{{name}}</code></p> <p>Look up <code>{{name}}</code></p>
<ul class="link-list"> <ul class="link-list">
<li><a target="_blank" href="https://www.shodan.io/domain/{{ name }}">… on shodan.io <small>(limited query's per day, wants an account)</small></a></li> <li><a target="_blank" href="https://www.shodan.io/domain/{{ name | urlencode_strict }}">… on shodan.io <small>(limited query's per day, wants an account)</small></a></li>
<li><a target="_blank" href="https://search.censys.io/search?resource=hosts&sort=RELEVANCE&per_page=25&virtual_hosts=EXCLUDE&q={{ name }}">… on search.censys.io <small>(10 query's per day, wants an account)</small></a></li> <li><a target="_blank" href="https://search.censys.io/search?resource=hosts&sort=RELEVANCE&per_page=25&virtual_hosts=EXCLUDE&q={{ name | urlencode_strict }}">… on search.censys.io <small>(10 query's per day, wants an account)</small></a></li>
<li><a target="_blank" href="https://www.virustotal.com/gui/domain/{{ name }}">… on virustotal.com</a></li> <li><a target="_blank" href="https://www.virustotal.com/gui/domain/{{ name | urlencode_strict }}">… on virustotal.com</a></li>
<li><a target="_blank" href="https://observatory.mozilla.org/analyze/{{ name }}">… on the Mozilla Observatory (http and tls checks)</a></li> <li><a target="_blank" href="https://observatory.mozilla.org/analyze/{{ name | urlencode_strict }}">… on the Mozilla Observatory (http and tls checks)</a></li>
<li><a target="_blank" href="https://internet.nl/site/{{ name }}">… on the Internet.nl Website test</a></li> <li><a target="_blank" href="https://internet.nl/site/{{ name | urlencode_strict }}">… on the Internet.nl Website test</a></li>
<li><a target="_blank" href="https://client.rdap.org/?type=domain&object={{ name }}">… on client.rdap.org <small>(a modern whois, make sure to allow xhr to 3rd parties)</small></a></li> <li><a target="_blank" href="https://client.rdap.org/?type=domain&object={{ name | urlencode_strict }}">… on client.rdap.org <small>(a modern whois, make sure to allow xhr to 3rd parties)</small></a></li>
<li><a target="_blank" href="https://crt.sh/?Identity={{ name | urlencode_strict }}&match==">… on crt.sh <small>(Certificate Transparancy Monitor)</small></a></li>
</ul> </ul>
{% endmacro domain_name_links %} {% endmacro domain_name_links %}
{% macro asn_links(asn, org_name="") %} {% macro asn_links(asn, org_name="") %}
<p>Look up <code>AS{{asn}}</code>{% if org_name%} ({{org_name}}){% endif %} …</p> <p>Look up <code>AS{{asn}}</code>{% if org_name%} ({{org_name}}){% endif %} …</p>
<ul class="link-list"> <ul class="link-list">
<li><a target="_blank" href="https://bgp.he.net/AS{{asn}}">… on Hurricane Electric BGP Toolkit</a></li> <li><a target="_blank" href="https://bgp.he.net/AS{{asn | urlencode_strict}}">… on Hurricane Electric BGP Toolkit</a></li>
<li><a target="_blank" href="https://radar.qrator.net/as{{asn}}">… on radar.qrator.net (BGP Tool)</a></li> <li><a target="_blank" href="https://radar.qrator.net/as{{asn | urlencode_strict}}">… on radar.qrator.net (BGP Tool)</a></li>
<li><a target="_blank" href="https://search.censys.io/search?resource=hosts&sort=RELEVANCE&per_page=25&virtual_hosts=EXCLUDE&q=autonomous_system.asn%3D{{asn}}">… on search.censys.io <small>(10 query's per day, wants an account)</small></a></li> <li><a target="_blank" href="https://search.censys.io/search?resource=hosts&sort=RELEVANCE&per_page=25&virtual_hosts=EXCLUDE&q=autonomous_system.asn%3D{{asn | urlencode_strict}}">… on search.censys.io <small>(10 query's per day, wants an account)</small></a></li>
<li><a target="_blank" href="https://client.rdap.org/?type=autnum&object={{ asn }}">… on client.rdap.org <small>(a modern whois, make sure to allow xhr to 3rd parties)</small></a></li> <li><a target="_blank" href="https://client.rdap.org/?type=autnum&object={{ asn }}">… on client.rdap.org <small>(a modern whois, make sure to allow xhr to 3rd parties)</small></a></li>
<li><a target="_blank" href="https://query.wikidata.org/#%23Select%20Wikipedia%20articles%20that%20belong%20to%20a%20given%20asn%0ASELECT%20DISTINCT%20%3Fitem%20%3Fwebsite%20%3FitemLabel%20%3FitemDescription%20%3Flang%20%3Farticle%20WHERE%20%7B%0A%20%20VALUES%20%3Fasn%20%7B%0A%20%20%20%20%22{{ asn }}%22%0A%20%20%7D%0A%20%20%3Fasn%20%5Ewdt%3AP3797%20%3Fitem.%0A%20%20OPTIONAL%20%7B%20%3Fitem%20wdt%3AP856%20%3Fwebsite.%20%7D%0A%20%20OPTIONAL%20%7B%0A%20%20%20%20%3Fitem%20%5Eschema%3Aabout%20%3Farticle.%0A%20%20%20%20%3Farticle%20schema%3AisPartOf%20_%3Ab64.%0A%20%20%20%20_%3Ab64%20wikibase%3AwikiGroup%20%22wikipedia%22.%0A%20%20%20%20%3Farticle%20schema%3AinLanguage%20%3Flang%3B%0A%20%20%20%20%20%20schema%3Aname%20%3Farticlename.%0A%20%20%20%20FILTER(((%3Flang%20%3D%20%22%5BAUTO_LANGUAGE%5D%22)%20%7C%7C%20(%3Flang%20%3D%20%22en%22))%20%7C%7C%20(%3Flang%20%3D%20%22de%22))%0A%20%20%7D%0A%20%20SERVICE%20wikibase%3Alabel%20%7B%0A%20%20%20%20bd%3AserviceParam%20wikibase%3Alanguage%20%22%5BAUTO_LANGUAGE%5D%2Cen%22.%0A%20%20%20%20%3Fitem%20rdfs%3Alabel%20%3FitemLabel%3B%0A%20%20%20%20%20%20schema%3Adescription%20%3FitemDescription.%0A%20%20%7D%0A%7D%0AORDER%20BY%20(UCASE(%3FitemLabel))">… on Wikidata and Wikipedia <small>(Press the run button in the sidebar to get results)</small></a></li> <li><a target="_blank" href="https://query.wikidata.org/#%23Select%20Wikipedia%20articles%20that%20belong%20to%20a%20given%20asn%0ASELECT%20DISTINCT%20%3Fitem%20%3Fwebsite%20%3FitemLabel%20%3FitemDescription%20%3Flang%20%3Farticle%20WHERE%20%7B%0A%20%20VALUES%20%3Fasn%20%7B%0A%20%20%20%20%22{{ asn | urlencode_strict }}%22%0A%20%20%7D%0A%20%20%3Fasn%20%5Ewdt%3AP3797%20%3Fitem.%0A%20%20OPTIONAL%20%7B%20%3Fitem%20wdt%3AP856%20%3Fwebsite.%20%7D%0A%20%20OPTIONAL%20%7B%0A%20%20%20%20%3Fitem%20%5Eschema%3Aabout%20%3Farticle.%0A%20%20%20%20%3Farticle%20schema%3AisPartOf%20_%3Ab64.%0A%20%20%20%20_%3Ab64%20wikibase%3AwikiGroup%20%22wikipedia%22.%0A%20%20%20%20%3Farticle%20schema%3AinLanguage%20%3Flang%3B%0A%20%20%20%20%20%20schema%3Aname%20%3Farticlename.%0A%20%20%20%20FILTER(((%3Flang%20%3D%20%22%5BAUTO_LANGUAGE%5D%22)%20%7C%7C%20(%3Flang%20%3D%20%22en%22))%20%7C%7C%20(%3Flang%20%3D%20%22de%22))%0A%20%20%7D%0A%20%20SERVICE%20wikibase%3Alabel%20%7B%0A%20%20%20%20bd%3AserviceParam%20wikibase%3Alanguage%20%22%5BAUTO_LANGUAGE%5D%2Cen%22.%0A%20%20%20%20%3Fitem%20rdfs%3Alabel%20%3FitemLabel%3B%0A%20%20%20%20%20%20schema%3Adescription%20%3FitemDescription.%0A%20%20%7D%0A%7D%0AORDER%20BY%20(UCASE(%3FitemLabel))">… on Wikidata and Wikipedia <small>(Press the run button in the sidebar to get results)</small></a></li>
</ul> </ul>
{% endmacro asn_links %} {% endmacro asn_links %}

View File

@ -380,6 +380,8 @@ a:visited {
color: var(--page-link-visited); color: var(--page-link-visited);
} }
a.sitename { display: inline-block; }
h1, a.sitename { h1, a.sitename {
margin: var(--heading-mg); margin: var(--heading-mg);
padding: var(--heading-pad); padding: var(--heading-pad);