From 4d4ee26a9b7f053ac8902fda90efaa8684a14468 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 4 Sep 2020 20:27:19 +0100 Subject: [PATCH] Saving domain roots in unknown file --- Cargo.lock | 24 ++++ service-providers/simple-socks5/Cargo.toml | 2 + .../simple-socks5/src/allowed_hosts.rs | 110 ++++++++++++++++-- service-providers/simple-socks5/src/core.rs | 22 +++- 4 files changed, 146 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6c445dc93..5829bc10a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -805,6 +805,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check 0.9.1", +] + [[package]] name = "extend" version = "0.1.1" @@ -2281,6 +2290,20 @@ dependencies = [ "tokio-test", ] +[[package]] +name = "publicsuffix" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" +dependencies = [ + "error-chain", + "idna 0.2.0", + "lazy_static", + "native-tls", + "regex", + "url 2.1.1", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2769,6 +2792,7 @@ dependencies = [ "ordered-buffer", "pretty_env_logger", "proxy-helpers", + "publicsuffix", "rand 0.7.3", "socks5-requests", "tokio 0.2.22", diff --git a/service-providers/simple-socks5/Cargo.toml b/service-providers/simple-socks5/Cargo.toml index 823bf8942c..5a6e5a73b3 100644 --- a/service-providers/simple-socks5/Cargo.toml +++ b/service-providers/simple-socks5/Cargo.toml @@ -14,7 +14,9 @@ log = "0.4" pretty_env_logger = "0.3" tokio = { version = "0.2", features = ["stream", "tcp", "rt-threaded", "macros"] } tokio-tungstenite = "0.11.0" +publicsuffix = "1.3.1" +# internal nymsphinx = { path = "../../common/nymsphinx" } ordered-buffer = {path = "../../common/socks5/ordered-buffer"} socks5-requests = { path = "../../common/socks5/requests" } diff --git a/service-providers/simple-socks5/src/allowed_hosts.rs b/service-providers/simple-socks5/src/allowed_hosts.rs index b414f0e1af..1ddc267391 100644 --- a/service-providers/simple-socks5/src/allowed_hosts.rs +++ b/service-providers/simple-socks5/src/allowed_hosts.rs @@ -1,5 +1,6 @@ use fs::OpenOptions; use io::BufReader; +use publicsuffix::{errors, List}; use std::fs; use std::fs::File; use std::io; @@ -11,7 +12,10 @@ use std::path::PathBuf; /// /// Requests to unknown hosts are automatically written to an `unknown_hosts` /// list so that they can be copy/pasted into the `allowed_hosts` list if desired. -struct OutboundRequestFilter { +/// This may be handy for service provider node operators who want to be able to look in the +/// `unknown_hosts` file and allow new hosts (e.g. if a wallet has added a new outbound request +/// which needs to be allowed). +pub(crate) struct OutboundRequestFilter { allowed_hosts: HostsStore, unknown_hosts: HostsStore, } @@ -27,29 +31,50 @@ impl OutboundRequestFilter { } } - /// Returns `true` if a host is in the `allowed_hosts` list. + /// Returns `true` if a host's domain is in the `allowed_hosts` list. /// /// If it's not in the list, return `false` and write it to the `unknown_hosts` storefile. pub(crate) fn check(&mut self, host: &str) -> bool { - if self.allowed_hosts.contains(host) { + let trimmed = Self::trim_port(host); + let domain_root = Self::get_domain_root(&trimmed).unwrap(); + if self.allowed_hosts.contains(&domain_root) { true } else { - self.unknown_hosts.maybe_add(host); + self.unknown_hosts.maybe_add(&domain_root); false } } + + fn trim_port(host: &str) -> String { + let mut tmp: Vec<&str> = host.split(":").collect(); + if tmp.len() > 1 { + tmp.pop(); // get rid of last element (port) + let out = tmp.join(":"); + out + } else { + host.to_string() + } + } + + /// Attempts to get the root domain, shorn of port, subdomains, etc. + fn get_domain_root(host: &str) -> Result { + let list = List::fetch()?; + let domain = list.parse_domain(host)?; + let root = domain.root().unwrap(); + Ok(root.to_string()) + } } /// A simple file-based store for information about allowed / unknown hosts. #[derive(Debug)] -struct HostsStore { +pub(crate) struct HostsStore { storefile: PathBuf, hosts: Vec, } impl HostsStore { /// Constructs a new HostsStore - fn new(base_dir: PathBuf, filename: PathBuf) -> HostsStore { + pub(crate) fn new(base_dir: PathBuf, filename: PathBuf) -> HostsStore { let storefile = HostsStore::setup_storefile(base_dir, filename); let hosts = HostsStore::load_from_storefile(&storefile).expect(&format!( "Could not load hosts from storefile at {:?}", @@ -72,10 +97,7 @@ impl HostsStore { } fn append_to_file(&self, host: &str) { - fs::write(&self.storefile, host).expect(&format!( - "Could not write to storage file at {:?}", - self.storefile - )); + HostsStore::append(&self.storefile, host); } fn contains(&self, host: &str) -> bool { @@ -92,7 +114,7 @@ impl HostsStore { } fn maybe_add(&mut self, host: &str) { - if !self.contains(&host) { + if !self.contains(host) { self.hosts.push(host.to_string()); self.append_to_file(host); } @@ -128,6 +150,64 @@ impl HostsStore { mod tests { use super::*; + #[cfg(test)] + mod trimming_port_information { + use super::*; + + #[test] + fn happens_when_port_exists() { + let host = "nymtech.net:9999"; + assert_eq!("nymtech.net", OutboundRequestFilter::trim_port(host)); + } + + #[test] + fn doesnt_happen_when_no_port_exists() { + let host = "nymtech.net"; + assert_eq!("nymtech.net", OutboundRequestFilter::trim_port(host)); + } + } + + #[cfg(test)] + mod getting_the_root_domain { + use super::*; + + #[test] + fn gets_a_com_tld_ok() { + let host = "domain.com"; + assert_eq!( + "domain.com", + OutboundRequestFilter::get_domain_root(host).unwrap() + ) + } + + #[test] + fn trims_subdomains() { + let host = "foomp.domain.com"; + assert_eq!( + "domain.com", + OutboundRequestFilter::get_domain_root(host).unwrap() + ) + } + + #[test] + fn works_for_non_com_roots() { + let host = "domain.co.uk"; + assert_eq!( + "domain.co.uk", + OutboundRequestFilter::get_domain_root(host).unwrap() + ) + } + + #[test] + fn works_for_non_com_roots_with_subdomains() { + let host = "foomp.domain.co.uk"; + assert_eq!( + "domain.co.uk", + OutboundRequestFilter::get_domain_root(host).unwrap() + ) + } + } + #[cfg(test)] mod requests_to_unknown_hosts { use super::*; @@ -180,6 +260,14 @@ mod tests { assert_eq!(true, filter.check(host)); } + #[test] + fn are_allowed_for_subdomains() { + let host = "foomp.nymtech.net"; + + let mut filter = setup(); + assert_eq!(true, filter.check(host)); + } + #[test] fn are_not_appended_to_file() { let mut filter = setup(); diff --git a/service-providers/simple-socks5/src/core.rs b/service-providers/simple-socks5/src/core.rs index b0e37cd737..d061d83de2 100644 --- a/service-providers/simple-socks5/src/core.rs +++ b/service-providers/simple-socks5/src/core.rs @@ -1,3 +1,4 @@ +use crate::allowed_hosts::{HostsStore, OutboundRequestFilter}; use crate::connection::Connection; use crate::websocket; use futures::channel::mpsc; @@ -8,6 +9,7 @@ use nymsphinx::addressing::clients::Recipient; use ordered_buffer::OrderedMessageBuffer; use proxy_helpers::connection_controller::{Controller, ControllerCommand}; use socks5_requests::{Request, Response}; +use std::path::PathBuf; use tokio::net::TcpStream; use tokio_tungstenite::tungstenite::protocol::Message; use tokio_tungstenite::WebSocketStream; @@ -16,11 +18,25 @@ use websocket_requests::{requests::ClientRequest, responses::ServerResponse}; pub struct ServiceProvider { listening_address: String, + outbound_request_filter: OutboundRequestFilter, } impl ServiceProvider { pub fn new(listening_address: String) -> ServiceProvider { - ServiceProvider { listening_address } + let allowed_hosts = HostsStore::new( + HostsStore::default_base_dir(), + PathBuf::from("allowed.list"), + ); + + let unknown_hosts = HostsStore::new( + HostsStore::default_base_dir(), + PathBuf::from("unknown.list"), + ); + let outbound_request_filter = OutboundRequestFilter::new(allowed_hosts, unknown_hosts); + ServiceProvider { + listening_address, + outbound_request_filter, + } } /// Listens for any messages from `mix_reader` that should be written back to the mix network @@ -112,6 +128,10 @@ impl ServiceProvider { message, return_address, } => { + if !self.outbound_request_filter.check(&remote_addr) { + continue; + } + let controller_sender_clone = controller_sender.clone(); let mut ordered_buffer = OrderedMessageBuffer::new(); ordered_buffer.write(message);