#![allow(dead_code)]
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::{Arc, Mutex};
use abuse_contact::Resolver;
use hickory_proto::op::{Message, MessageType, OpCode, ResponseCode};
use hickory_proto::rr::rdata::{A, MX, TXT};
use hickory_proto::rr::{Name, RData, Record, RecordType};
use tokio::net::UdpSocket;
use tokio::task::JoinHandle;
#[derive(Clone)]
pub enum Held {
Txt(Vec<Vec<&'static str>>),
Mx(Vec<(u16, &'static str)>),
A(Ipv4Addr),
ServFail,
}
pub struct Server {
address: SocketAddr,
asked: Arc<Mutex<Vec<(String, RecordType)>>>,
task: JoinHandle<()>,
}
impl Drop for Server {
fn drop(&mut self) {
self.task.abort();
}
}
impl Server {
pub async fn start(zone: Vec<(&'static str, RecordType, Held)>) -> Self {
let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let address = socket.local_addr().unwrap();
let asked = Arc::new(Mutex::new(Vec::new()));
let log = Arc::clone(&asked);
let task = tokio::spawn(async move {
let mut buffer = [0u8; 1500];
loop {
let Ok((length, peer)) = socket.recv_from(&mut buffer).await else {
return;
};
let Ok(query) = Message::from_vec(&buffer[..length]) else {
continue;
};
let answer = answer(&query, &zone, &log);
if socket.send_to(&answer, peer).await.is_err() {
return;
}
}
});
Self {
address,
asked,
task,
}
}
pub fn resolver(&self) -> Resolver {
Resolver::with_nameservers(&[self.address]).unwrap()
}
pub fn asked(&self) -> Vec<(String, RecordType)> {
self.asked.lock().unwrap().clone()
}
}
fn answer(
query: &Message,
zone: &[(&'static str, RecordType, Held)],
log: &Mutex<Vec<(String, RecordType)>>,
) -> Vec<u8> {
let mut response = Message::new(query.metadata.id, MessageType::Response, OpCode::Query);
response.metadata.recursion_desired = query.metadata.recursion_desired;
response.metadata.recursion_available = true;
let Some(question) = query.queries.first() else {
response.metadata.response_code = ResponseCode::FormErr;
return response.to_vec().unwrap();
};
response.queries.push(question.clone());
let asked_name = question.name.to_ascii().to_lowercase();
let asked_type = question.query_type;
log.lock().unwrap().push((asked_name.clone(), asked_type));
let at_name: Vec<_> = zone
.iter()
.filter(|(name, _, _)| format!("{}.", name.trim_end_matches('.')) == asked_name)
.collect();
if at_name.is_empty() {
response.metadata.response_code = ResponseCode::NXDomain;
return response.to_vec().unwrap();
}
for (_, record_type, held) in at_name {
if *record_type != asked_type {
continue;
}
let owner = question.name.clone();
match held {
Held::ServFail => {
response.metadata.response_code = ResponseCode::ServFail;
return response.to_vec().unwrap();
}
Held::Txt(records) => {
for strings in records {
let data = TXT::new(strings.iter().map(|s| (*s).to_owned()).collect());
response
.answers
.push(Record::from_rdata(owner.clone(), 60, RData::TXT(data)));
}
}
Held::Mx(records) => {
for (preference, host) in records {
let data = MX::new(*preference, host.parse::<Name>().unwrap());
response
.answers
.push(Record::from_rdata(owner.clone(), 60, RData::MX(data)));
}
}
Held::A(ip) => {
response
.answers
.push(Record::from_rdata(owner.clone(), 60, RData::A(A(*ip))));
}
}
}
response.to_vec().unwrap()
}
pub fn emails(contacts: &[abuse_contact::Contact]) -> Vec<&str> {
contacts
.iter()
.map(|contact| contact.email.as_str())
.collect()
}