Move hostname enforcement into TorAddr.
This commit is contained in:
parent
5ae433c747
commit
0750199a8c
|
@ -82,6 +82,33 @@ impl TorAddr {
|
|||
let port = self.port;
|
||||
(host, port)
|
||||
}
|
||||
|
||||
/// Return true if the `host` in this address is local.
|
||||
fn is_local(&self) -> bool {
|
||||
self.host.is_local()
|
||||
}
|
||||
|
||||
/// Give an error if this address doesn't conform to the rules set in
|
||||
/// `cfg`.
|
||||
pub(crate) fn enforce_config(
|
||||
&self,
|
||||
cfg: &crate::config::ClientConfig,
|
||||
) -> Result<(), crate::Error> {
|
||||
if !cfg.allow_local_addrs && self.is_local() {
|
||||
return Err(crate::Error::LocalAddress);
|
||||
}
|
||||
|
||||
if let Host::Hostname(addr) = &self.host {
|
||||
if !is_valid_hostname(addr) {
|
||||
return Err(crate::Error::InvalidHostname);
|
||||
}
|
||||
if addr.to_lowercase().ends_with(".onion") {
|
||||
return Err(crate::Error::OnionAddressNotSupported);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TorAddr {
|
||||
|
@ -130,6 +157,19 @@ impl FromStr for Host {
|
|||
}
|
||||
}
|
||||
|
||||
impl Host {
|
||||
/// Return true if this address is one that is "internal": that is,
|
||||
/// relative to the particular host that is resolving it.
|
||||
fn is_local(&self) -> bool {
|
||||
match self {
|
||||
Host::Hostname(name) => name.eq_ignore_ascii_case("localhost"),
|
||||
// TODO: use is_global once it's stable.
|
||||
Host::Ip(IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private(),
|
||||
Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Host {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
|
@ -244,3 +284,92 @@ impl DangerouslyIntoTorAddr for SocketAddrV6 {
|
|||
(*addr, port).into_tor_addr_dangerously()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether `hostname` is a valid hostname or not.
|
||||
///
|
||||
/// TODO: Check whether the rules given here are in fact the same rules
|
||||
/// as Tor follows, and whether they conform to anything.
|
||||
fn is_valid_hostname(hostname: &str) -> bool {
|
||||
/// Check if we have the valid characters for a hostname
|
||||
fn is_valid_char(byte: u8) -> bool {
|
||||
((b'a'..=b'z').contains(&byte))
|
||||
|| ((b'A'..=b'Z').contains(&byte))
|
||||
|| ((b'0'..=b'9').contains(&byte))
|
||||
|| byte == b'-'
|
||||
|| byte == b'.'
|
||||
}
|
||||
|
||||
/// Check if we look like an IPv6 address
|
||||
fn is_ipv6_str(addr: &str) -> bool {
|
||||
if let Ok(ip) = IpAddr::from_str(addr) {
|
||||
ip.is_ipv6()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
!(hostname.bytes().any(|byte| !is_valid_char(byte))
|
||||
|| hostname.ends_with('-')
|
||||
|| hostname.starts_with('-')
|
||||
|| hostname.ends_with('.')
|
||||
|| hostname.starts_with('.')
|
||||
|| hostname.is_empty())
|
||||
|| is_ipv6_str(hostname)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validate_hostname() {
|
||||
// Valid hostname tests
|
||||
assert!(is_valid_hostname("torproject.org"));
|
||||
assert!(is_valid_hostname("Tor-Project.org"));
|
||||
|
||||
// Invalid hostname tests
|
||||
assert!(!is_valid_hostname("-torproject.org"));
|
||||
assert!(!is_valid_hostname("_torproject.org"));
|
||||
assert!(!is_valid_hostname("tor_project1.org"));
|
||||
assert!(!is_valid_hostname("iwanna$money.org"));
|
||||
assert!(!is_valid_hostname(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_addr() {
|
||||
fn ok<A: IntoTorAddr>(addr: A) -> bool {
|
||||
if let Ok(toraddr) = addr.into_tor_addr() {
|
||||
toraddr.enforce_config(&Default::default()).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
assert!(ok("[2001:db8::42]:20"));
|
||||
assert!(ok(("2001:db8::42", 20)));
|
||||
assert!(ok(("128.66.0.42", 443)));
|
||||
assert!(ok("128.66.0.42:443"));
|
||||
assert!(ok("www.torproject.org:443"));
|
||||
assert!(ok(("www.torproject.org", 443)));
|
||||
|
||||
assert!(!ok("-foobar.net:443"));
|
||||
assert!(!ok("www.torproject.org"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_addrs() {
|
||||
fn is_local_hostname(s: &str) -> bool {
|
||||
let h: Host = s.parse().unwrap();
|
||||
h.is_local()
|
||||
}
|
||||
|
||||
assert!(is_local_hostname("localhost"));
|
||||
assert!(is_local_hostname("loCALHOST"));
|
||||
assert!(is_local_hostname("127.0.0.1"));
|
||||
assert!(is_local_hostname("::1"));
|
||||
assert!(is_local_hostname("192.168.0.1"));
|
||||
|
||||
assert!(!is_local_hostname("www.example.com"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,18 +201,9 @@ impl<R: Runtime> TorClient<R> {
|
|||
flags: Option<ConnectPrefs>,
|
||||
) -> Result<DataStream> {
|
||||
let addr = target.into_tor_addr()?;
|
||||
addr.enforce_config(&self.clientcfg)?;
|
||||
let (addr, port) = addr.into_string_and_port();
|
||||
|
||||
if addr.to_lowercase().ends_with(".onion") {
|
||||
return Err(Error::OnionAddressNotSupported);
|
||||
}
|
||||
if !is_valid_hostname(&addr) {
|
||||
return Err(Error::InvalidHostname);
|
||||
}
|
||||
if !self.clientcfg.allow_local_addrs && is_local_hostname(&addr) {
|
||||
return Err(Error::LocalAddress);
|
||||
}
|
||||
|
||||
let flags = flags.unwrap_or_default();
|
||||
let exit_ports = [flags.wrap_target_port(port)];
|
||||
let circ = self.get_or_launch_exit_circ(&exit_ports, &flags).await?;
|
||||
|
@ -236,12 +227,8 @@ impl<R: Runtime> TorClient<R> {
|
|||
hostname: &str,
|
||||
flags: Option<ConnectPrefs>,
|
||||
) -> Result<Vec<IpAddr>> {
|
||||
if hostname.to_lowercase().ends_with(".onion") {
|
||||
return Err(Error::OnionAddressNotSupported);
|
||||
}
|
||||
if !is_valid_hostname(hostname) {
|
||||
return Err(Error::InvalidHostname);
|
||||
}
|
||||
let addr = (hostname, 0).into_tor_addr()?;
|
||||
addr.enforce_config(&self.clientcfg)?;
|
||||
|
||||
let flags = flags.unwrap_or_default();
|
||||
let circ = self.get_or_launch_exit_circ(&[], &flags).await?;
|
||||
|
@ -325,54 +312,6 @@ impl<R: Runtime> TorClient<R> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Validate if we are an valid hostname or not
|
||||
fn is_valid_hostname(hostname: &str) -> bool {
|
||||
/// Check if we have the valid characters for a hostname
|
||||
fn is_valid_char(byte: u8) -> bool {
|
||||
((b'a'..=b'z').contains(&byte))
|
||||
|| ((b'A'..=b'Z').contains(&byte))
|
||||
|| ((b'0'..=b'9').contains(&byte))
|
||||
|| byte == b'-'
|
||||
|| byte == b'.'
|
||||
}
|
||||
|
||||
/// Check if we look like an IPv6 address
|
||||
fn is_ipv6_str(addr: &str) -> bool {
|
||||
if let Ok(ip) = IpAddr::from_str(addr) {
|
||||
ip.is_ipv6()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
!(hostname.bytes().any(|byte| !is_valid_char(byte))
|
||||
|| hostname.ends_with('-')
|
||||
|| hostname.starts_with('-')
|
||||
|| hostname.ends_with('.')
|
||||
|| hostname.starts_with('.')
|
||||
|| hostname.is_empty())
|
||||
|| is_ipv6_str(hostname)
|
||||
}
|
||||
|
||||
/// Return true if `addr` refers to a local address.
|
||||
fn is_local_hostname(addr: &str) -> bool {
|
||||
if addr.eq_ignore_ascii_case("localhost") {
|
||||
true
|
||||
} else if let Ok(ip) = IpAddr::from_str(addr) {
|
||||
is_internal_ip(&ip)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Check if the IP is internal
|
||||
fn is_internal_ip(addr: &IpAddr) -> bool {
|
||||
// TODO: Use is_global() when it is stable
|
||||
match addr {
|
||||
IpAddr::V4(ip) => ip.is_loopback() || ip.is_private(),
|
||||
IpAddr::V6(ip) => ip.is_loopback(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whenever a [`DirEvent::NewConsensus`] arrives on `events`, update
|
||||
/// `circmgr` with the consensus parameters from `dirmgr`.
|
||||
///
|
||||
|
@ -489,36 +428,3 @@ impl<R: Runtime> Drop for TorClient<R> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validate_hostname() {
|
||||
// Valid hostname tests
|
||||
assert!(is_valid_hostname("torproject.org"));
|
||||
assert!(is_valid_hostname("Tor-Project.org"));
|
||||
assert!(is_valid_hostname("72.0.227.52"));
|
||||
assert!(is_valid_hostname("2600::1"));
|
||||
|
||||
// Invalid hostname tests
|
||||
assert!(!is_valid_hostname("-torproject.org"));
|
||||
assert!(!is_valid_hostname("_torproject.org"));
|
||||
assert!(!is_valid_hostname("tor_project1.org"));
|
||||
assert!(!is_valid_hostname("iwanna$money.org"));
|
||||
assert!(!is_valid_hostname(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_addrs() {
|
||||
assert!(is_local_hostname("localhost"));
|
||||
assert!(is_local_hostname("loCALHOST"));
|
||||
assert!(is_local_hostname("127.0.0.1"));
|
||||
assert!(is_local_hostname("::1"));
|
||||
assert!(is_local_hostname("192.168.0.1"));
|
||||
|
||||
assert!(!is_local_hostname("www.example.com"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue