Added IDN support

This commit is contained in:
Slatian 2023-02-23 00:58:38 +01:00
parent e3054e0158
commit d202ebb14e
5 changed files with 104 additions and 8 deletions

1
Cargo.lock generated
View File

@ -1190,6 +1190,7 @@ dependencies = [
"axum", "axum",
"axum-client-ip", "axum-client-ip",
"clap", "clap",
"idna 0.3.0",
"lazy_static", "lazy_static",
"maxminddb", "maxminddb",
"regex", "regex",

View File

@ -10,6 +10,7 @@ authors = ["Slatian <baschdel@disroot.org>"]
axum = { version = "0.6", features = ["macros", "headers"] } axum = { version = "0.6", features = ["macros", "headers"] }
axum-client-ip = "0.4" axum-client-ip = "0.4"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
idna = "0.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.7" regex = "1.7"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

76
src/idna.rs Normal file
View File

@ -0,0 +1,76 @@
/*
* The prupose of this module is to analyse a given string
* for being a IDNA domain name and decodes it,
* it is also able to return an encoded form if given unicode.
* The result is supposed to be used in a template
*/
use serde::{Deserialize, Serialize};
use ::idna;
#[derive(Deserialize, Serialize, Copy, Default, Clone, PartialEq)]
#[serde(rename_all="lowercase")]
pub enum NameType {
Ascii,
#[default]
Unicode,
IDNA,
}
// Note, that the
#[derive(Deserialize, Serialize, Default, Clone)]
pub struct IdnaName {
pub unicode: String,
// if null the unicode version only contains ascii range chars,
// not neccessary to encode
pub idna: Option<String>,
pub original_was: NameType,
#[serde(skip_serializing_if = "Option::is_none")]
pub decoder_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoder_error: Option<String>,
}
impl IdnaName {
pub fn from_string(s: &String) -> Self {
let mut original_was = NameType::Unicode;
let unicode: String;
let decoder_error;
if s.starts_with("xn--") && s.is_ascii() {
original_was = NameType::IDNA;
let (uc, ures) = idna::domain_to_unicode(s);
unicode = uc;
decoder_error = ures.map_or_else(|e| Some(e.to_string()), |_| None);
} else {
unicode = s.clone();
decoder_error = None;
};
let (idna, encoder_error) = match idna::domain_to_ascii_strict(s) {
Ok(idna) => {
if &idna != s || original_was == NameType::IDNA{
(Some(idna), None)
} else {
original_was = NameType::Ascii;
(None, None)
}
},
Err(e) => {
(None, Some(e.to_string()))
}
};
IdnaName {
unicode: unicode,
idna: idna,
original_was: original_was,
decoder_error: decoder_error,
encoder_error: encoder_error,
}
}
pub fn was_ascii(&self) -> bool {
return self.original_was == NameType::Ascii;
}
}

View File

@ -35,11 +35,15 @@ mod geoip;
mod ipinfo; mod ipinfo;
mod simple_dns; mod simple_dns;
mod templating_engine; mod templating_engine;
mod idna;
use crate::geoip::QueryAsn; use crate::geoip::{
use crate::geoip::QueryLocation; QueryAsn,
use geoip::AsnResult; QueryLocation,
use geoip::LocationResult; AsnResult,
LocationResult,
};
use crate::idna::IdnaName;
use crate::templating_engine::{ use crate::templating_engine::{
View, View,
@ -69,6 +73,13 @@ pub struct IpResult {
ip_info: AddressInfo, ip_info: AddressInfo,
} }
#[derive(serde::Deserialize, serde::Serialize, Default, Clone)]
pub struct DigResult {
records: simple_dns::DnsLookupResult,
#[serde(skip_serializing_if = "IdnaName::was_ascii")]
idna: IdnaName,
}
struct ServiceSharedState { struct ServiceSharedState {
templating_engine: templating_engine::Engine, templating_engine: templating_engine::Engine,
dns_resolver: TokioAsyncResolver, dns_resolver: TokioAsyncResolver,
@ -467,11 +478,18 @@ async fn handle_dig_request(
async fn get_dig_result( async fn get_dig_result(
dig_query: &String, dig_query: &String,
state: &ServiceSharedState, state: &ServiceSharedState,
) -> simple_dns::DnsLookupResult { ) -> DigResult {
let name = &dig_query.trim().trim_end_matches(".").to_string(); let name = &dig_query.trim().trim_end_matches(".").to_string();
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) { if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
Default::default() Default::default()
} else { } else {
simple_dns::lookup(&state.dns_resolver, name, true).await let idna_name = IdnaName::from_string(&name);
DigResult {
records: simple_dns::lookup(
&state.dns_resolver,
&(idna_name.idna.clone().unwrap_or(name.to_owned())+"."),
true).await,
idna: idna_name,
}
} }
} }

View File

@ -13,7 +13,7 @@ use axum::{
use tera::Tera; use tera::Tera;
use toml::Table; use toml::Table;
use crate::simple_dns; use crate::DigResult;
use crate::IpResult; use crate::IpResult;
/* Response format */ /* Response format */
@ -52,7 +52,7 @@ pub struct TemplateSettings {
#[serde(untagged)] #[serde(untagged)]
pub enum View { pub enum View {
Asn { asn: u32 }, Asn { asn: u32 },
Dig { query: String, result: simple_dns::DnsLookupResult }, Dig { query: String, result: DigResult },
Index { result: IpResult, user_agent: Option<String> }, Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult }, Ip { result: IpResult },
Message{ title: String, message: String }, Message{ title: String, message: String },