use anyhow::{bail, Context, Result};
use std::net::Ipv4Addr;
use crate::server::netbios_ns::packet as pkt;
pub const RECV_BUFFER: usize = pkt::MAX_DATAGRAM;
pub fn suffix_label(suffix: u8) -> &'static str {
match suffix {
0x00 => "workstation",
0x01 => "messenger_or_master_browser",
0x03 => "messenger",
0x06 => "ras_server",
0x1b => "domain_master_browser",
0x1c => "domain_controllers",
0x1d => "master_browser",
0x1e => "browser_service_elections",
0x1f => "net_dde",
0x20 => "file_server",
0x21 => "ras_client",
0x22 => "exchange_interchange",
0x23 => "exchange_store",
0x24 => "exchange_directory",
0x30 => "modem_sharing_server",
0x31 => "modem_sharing_client",
0x43 => "sms_client_remote_control",
0x44 => "sms_admin_remote_transfer",
0x45 => "sms_client_remote_chat",
0x46 => "sms_client_remote_transfer",
0x4c => "dec_pathworks_tcpip",
0x52 => "dec_pathworks_tcpip",
0x6a => "exchange_im_service",
0x87 => "exchange_ms_mail_connector",
0xbe => "network_monitor_agent",
0xbf => "network_monitor_utility",
_ => "unknown",
}
}
pub fn rcode_label(rcode: u16) -> &'static str {
match rcode {
pkt::RCODE_OK => "ok",
pkt::RCODE_FMT_ERR => "format_error",
pkt::RCODE_SRV_ERR => "server_failure",
pkt::RCODE_NAM_ERR => "name_not_found",
pkt::RCODE_IMP_ERR => "unsupported_request",
pkt::RCODE_RFS_ERR => "refused",
pkt::RCODE_ACT_ERR => "name_active",
pkt::RCODE_CFT_ERR => "name_conflict",
_ => "unknown",
}
}
pub fn node_type_label(flags: u16) -> &'static str {
match (flags >> 13) & 0b11 {
0 => "b",
1 => "p",
2 => "m",
_ => "h",
}
}
pub fn encode_name_query(trn_id: u16, name: &str, suffix: u8, broadcast: bool) -> Result<Vec<u8>> {
let flags = if broadcast {
pkt::NM_FLAG_B | pkt::NM_FLAG_RD
} else {
0
};
encode_query(trn_id, flags, pkt::QTYPE_NB, name, suffix)
}
pub fn encode_node_status_query(trn_id: u16, name: &str, suffix: u8) -> Result<Vec<u8>> {
encode_query(trn_id, 0, pkt::QTYPE_NBSTAT, name, suffix)
}
fn encode_query(trn_id: u16, flags: u16, qtype: u16, name: &str, suffix: u8) -> Result<Vec<u8>> {
let header = pkt::Header {
trn_id,
flags,
qdcount: 1,
ancount: 0,
nscount: 0,
arcount: 0,
};
let name_field = pkt::encode_name_field(name, suffix, None)?;
let mut out = Vec::with_capacity(pkt::HEADER_LEN + name_field.len() + 4);
out.extend_from_slice(&header.encode());
out.extend_from_slice(&name_field);
out.extend_from_slice(&qtype.to_be_bytes());
out.extend_from_slice(&pkt::CLASS_IN.to_be_bytes());
Ok(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameAnswer {
pub name: String,
pub suffix: u8,
pub addresses: Vec<Ipv4Addr>,
pub ttl: u32,
pub group: bool,
pub node_type: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeStatusName {
pub name: String,
pub suffix: u8,
pub group: bool,
pub active: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeStatusAnswer {
pub name: String,
pub suffix: u8,
pub names: Vec<NodeStatusName>,
pub mac_address: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NegativeAnswer {
pub name: String,
pub suffix: u8,
pub rcode: u16,
pub rcode_name: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NbnsAnswer {
Name(NameAnswer),
NodeStatus(NodeStatusAnswer),
Negative(NegativeAnswer),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NbnsResponse {
pub trn_id: u16,
pub answer: NbnsAnswer,
}
pub fn parse_response(buf: &[u8]) -> Result<NbnsResponse> {
if buf.len() > pkt::MAX_DATAGRAM {
bail!(
"NBNS datagram is {} octets; RFC 1002 §4.1 caps it at {}",
buf.len(),
pkt::MAX_DATAGRAM
);
}
let header = pkt::Header::parse(buf)?;
if !header.is_response() {
bail!("datagram is a request (R=0), not a response");
}
let mut cursor = pkt::HEADER_LEN;
for _ in 0..header.qdcount {
let question = pkt::read_name_field(buf, cursor)?;
cursor = question
.end
.checked_add(4)
.context("response truncated inside an echoed question")?;
}
let rcode = header.rcode();
if header.ancount == 0 {
if rcode != pkt::RCODE_OK {
return Ok(NbnsResponse {
trn_id: header.trn_id,
answer: NbnsAnswer::Negative(NegativeAnswer {
name: String::new(),
suffix: 0,
rcode,
rcode_name: rcode_label(rcode),
}),
});
}
bail!("NBNS response has RCODE 0 and ANCOUNT 0: it asserts nothing");
}
let rr_name = pkt::read_name_field(buf, cursor)?;
let mut p = rr_name.end;
if p + 10 > buf.len() {
bail!("NBNS response truncated inside the answer record header");
}
let rr_type = u16::from_be_bytes([buf[p], buf[p + 1]]);
let ttl = u32::from_be_bytes([buf[p + 4], buf[p + 5], buf[p + 6], buf[p + 7]]);
let rdlength = u16::from_be_bytes([buf[p + 8], buf[p + 9]]) as usize;
p += 10;
let rdata = buf
.get(p..p + rdlength)
.context("NBNS response truncated inside the answer RDATA")?;
if rcode != pkt::RCODE_OK {
return Ok(NbnsResponse {
trn_id: header.trn_id,
answer: NbnsAnswer::Negative(NegativeAnswer {
name: rr_name.name,
suffix: rr_name.suffix,
rcode,
rcode_name: rcode_label(rcode),
}),
});
}
let answer = match rr_type {
pkt::QTYPE_NB => {
NbnsAnswer::Name(parse_nb_rdata(rr_name.name, rr_name.suffix, ttl, rdata)?)
}
pkt::QTYPE_NBSTAT => {
NbnsAnswer::NodeStatus(parse_nbstat_rdata(rr_name.name, rr_name.suffix, rdata)?)
}
pkt::RRTYPE_NULL => bail!(
"NBNS response carries a NULL answer record with RCODE 0, which asserts nothing \
(RFC 1002 §4.2.14 requires a non-zero RCODE on a negative response)"
),
other => bail!(
"unsupported NBNS answer record type 0x{:04x} ({})",
other,
pkt::qtype_name(other)
),
};
Ok(NbnsResponse {
trn_id: header.trn_id,
answer,
})
}
fn parse_nb_rdata(name: String, suffix: u8, ttl: u32, rdata: &[u8]) -> Result<NameAnswer> {
if rdata.is_empty() || !rdata.len().is_multiple_of(6) {
bail!(
"NB answer RDATA is {} octets; RFC 1002 §4.2.13 requires a non-zero multiple of 6 \
(2 flag octets + 4 address octets per entry)",
rdata.len()
);
}
let mut addresses = Vec::with_capacity(rdata.len() / 6);
let mut first_flags = 0u16;
for (i, chunk) in rdata.as_chunks::<6>().0.iter().enumerate() {
let flags = u16::from_be_bytes([chunk[0], chunk[1]]);
if i == 0 {
first_flags = flags;
}
addresses.push(Ipv4Addr::new(chunk[2], chunk[3], chunk[4], chunk[5]));
}
Ok(NameAnswer {
name,
suffix,
addresses,
ttl,
group: first_flags & pkt::NB_FLAG_GROUP != 0,
node_type: node_type_label(first_flags),
})
}
fn parse_nbstat_rdata(name: String, suffix: u8, rdata: &[u8]) -> Result<NodeStatusAnswer> {
let num_names = *rdata
.first()
.context("node status RDATA is empty; NUM_NAMES is missing")? as usize;
let names_len = num_names
.checked_mul(18)
.context("node status NUM_NAMES overflows the name list length")?;
let needed = 1 + names_len + pkt::STATISTICS_LEN;
if rdata.len() < needed {
bail!(
"node status RDATA is {} octets but NUM_NAMES={} needs {} \
(1 + 18 per name + {} of statistics)",
rdata.len(),
num_names,
needed,
pkt::STATISTICS_LEN
);
}
let mut names = Vec::with_capacity(num_names);
for i in 0..num_names {
let at = 1 + i * 18;
let mut raw = [0u8; pkt::NAME_LEN];
raw.copy_from_slice(&rdata[at..at + pkt::NAME_LEN]);
let flags = u16::from_be_bytes([rdata[at + 16], rdata[at + 17]]);
let (entry_name, entry_suffix) = pkt::split_netbios_name(&raw);
names.push(NodeStatusName {
name: entry_name,
suffix: entry_suffix,
group: flags & pkt::NAME_FLAG_GROUP != 0,
active: flags & pkt::NAME_FLAG_ACTIVE != 0,
});
}
let stats_at = 1 + names_len;
let mut mac = [0u8; 6];
mac.copy_from_slice(&rdata[stats_at..stats_at + 6]);
Ok(NodeStatusAnswer {
name,
suffix,
names,
mac_address: pkt::format_mac(&mac),
})
}
pub fn parse_suffix(value: Option<&serde_json::Value>) -> Result<u8> {
pkt::parse_suffix_value(value)
}