added port privacy

This commit is contained in:
Ellie D 2019-06-01 13:10:20 -05:00
parent 67529fdb0d
commit 5941fe4728
2 changed files with 89 additions and 18 deletions

View File

@ -42,6 +42,23 @@ parse each line as an IP address (v4 or v6 are both fine), and immediately drop
connections from those addresses.
### How to make ports private
In case you want a port only to be accessible locally - e.g. if you're running a streaming server
and don't want anyone else to be able to inject data into the stream - you can mark them private
by suffixing them with the letter `p`, e.g.
`sunbeam 4444p 55555`
Additionaly, ports suffixed with `w` only accept connections from IP addresses listed in the file
`.yesunbeam`, which acts as a whitelist in a similar fashion to the `.nosunbeam` blacklist.
Entries in the blacklist take precedence over entries in the whitelist, so addresses listed in
both places will not be able to connect.
Using `w` with an empty or missing `.yesunbeam` file is nearly equivalent to using `p`, with the
difference being that `p` causes the socket to bind to `127.0.0.1` instead of the public address,
while `w` keeps the binding public but checks all incoming connections against a hash table of
allowed addresses and drops them if they are not found. `p` is likely a little bit faster, but
only `w` will print a notification to the console whenever a connection is rejected.
If a port is suffixed with both `w` and `p`, whichever is listed last will take precedence.
### Why you can't give it domain names
Rust has a minimal standard library. One consequence of this is the fact that there's no way to
resolve a domain name without depending on external crates or writing an entire DNS client from

View File

@ -14,6 +14,16 @@ static BUFFERSIZE_INITIAL:usize = 511;
static LOOPBACK_FLAG:&str = "-l";
static BLOCKFILE_NAME:&str = ".nosunbeam";
static ALLOWFILE_NAME:&str = ".yesunbeam";
#[derive(PartialEq)]
#[derive(Clone)]
#[derive(Copy)]
enum PortPrivacy {
Public,
Whitelist,
Private,
}
fn main() {
let argv = args().collect::<Vec<String>>();
@ -23,21 +33,29 @@ fn main() {
}
let mut loopbackmode:bool = false;
let mut addresses:Vec<SocketAddr> = Vec::new();
let mut ports:Vec<u16> = Vec::new();
let mut ports:Vec<(PortPrivacy,u16)> = Vec::new();
for arg in argv[1..].iter() {
if arg == LOOPBACK_FLAG {
loopbackmode = true;
} else {
match arg.parse() {
match arg.trim_end_matches(&['p','w'][..]).parse::<u16>() {
Err(_why) => {
match arg.parse() {
match arg.parse::<SocketAddr>() {
Err(_why) => {
eprintln!("-!- could not parse `{}` as a socket address or port number",arg);
},
Ok(address) => addresses.push(address),
};
},
Ok(n) => ports.push(n),
Ok(n) => if arg.ends_with("p") {
println!("-i- accepting only local connections on port {}",n);
ports.push((PortPrivacy::Private,n));
} else if arg.ends_with("w") {
println!("-i- accepting only connections whitelisted in {} on port {}",ALLOWFILE_NAME,n);
ports.push((PortPrivacy::Whitelist,n));
} else {
ports.push((PortPrivacy::Public,n));
},
};
}
}
@ -76,16 +94,50 @@ fn main() {
},
};
let mut connectors:VecDeque<(u16,TcpListener,VecDeque<(TcpStream,SocketAddr)>)> = VecDeque::new();
for port in ports.iter() {
match TcpListener::bind(&format!("[::]:{}",port)) {
let mut allowlist:HashSet<IpAddr> = HashSet::new();
match File::open(&ALLOWFILE_NAME) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => (),
_ => {
eprintln!("-!- failed to open {} to read IP address allowing rules: {}",ALLOWFILE_NAME,why);
exit(1);
},
},
Ok(mut file) => {
let mut allowstring = String::new();
match file.read_to_string(&mut allowstring) {
Err(why) => {
eprintln!("-!- failed to read {} for IP address allowing rules: {}",ALLOWFILE_NAME,why);
exit(1);
},
Ok(_) => (),
};
for line in allowstring.lines() {
match line.parse::<IpAddr>() {
Err(_why) => {
eprintln!("-!- could not parse '{}' in {} as an IP address",line,ALLOWFILE_NAME);
exit(1);
},
Ok(address) => allowlist.insert(address),
};
}
},
};
let mut connectors:VecDeque<(PortPrivacy,u16,TcpListener,VecDeque<(TcpStream,SocketAddr)>)> = VecDeque::new();
for (privacy,port) in ports.iter() {
let bindaddress:&str = match privacy {
PortPrivacy::Private => "127.0.0.1",
_ => "[::]",
};
match TcpListener::bind(&format!("{}:{}",bindaddress,port)) {
Err(why) => {
eprintln!("-!- failed to bind TCP listener: {}",why);
exit(1);
},
Ok(listener) => {
listener.set_nonblocking(true).expect("cannot set listener to nonblocking");
connectors.push_back((*port,listener,VecDeque::new()));
connectors.push_back((*privacy,*port,listener,VecDeque::new()));
},
};
}
@ -141,7 +193,7 @@ fn main() {
if bottle.len() > 0 {
idlecycles = 0;
}
for (_port,_listener,otherconnects) in connectors.iter_mut() {
for (_privacy,_port,_listener,otherconnects) in connectors.iter_mut() {
for (otherstream,_otheraddress) in otherconnects.iter_mut() {
let _ = otherstream.write_all(&bottle);
}
@ -149,7 +201,7 @@ fn main() {
}
bottle.clear();
if let Some((port,listener,mut connections)) = connectors.pop_front() {
if let Some((privacy,port,listener,mut connections)) = connectors.pop_front() {
match listener.accept() {
Err(why) => match why.kind() {
io::ErrorKind::WouldBlock => (),
@ -159,7 +211,9 @@ fn main() {
},
Ok((stream,address)) => {
if blocklist.contains(&address.ip()) {
println!("-x- refusing connection to {} (listed in {})",address,BLOCKFILE_NAME);
println!("-x- refusing connection to {} on port {} (listed in {})",address,port,BLOCKFILE_NAME);
} else if privacy == PortPrivacy::Whitelist && !address.ip().is_loopback() && !allowlist.contains(&address.ip()) {
println!("-x- refusing connection to {} on port {} (not listed in {})",address,port,ALLOWFILE_NAME);
} else {
println!("--> connection opened by {} on port {}",address,port);
stream.set_nonblocking(true).expect("cannot set stream to nonblocking");
@ -190,7 +244,7 @@ fn main() {
if bottle.len() > 0 {
idlecycles = 0;
}
for (_port,_listener,otherconnects) in connectors.iter_mut() {
for (_privacy,_port,_listener,otherconnects) in connectors.iter_mut() {
for (otherstream,_otheraddress) in otherconnects.iter_mut() {
let _ = otherstream.write_all(&bottle);
}
@ -199,7 +253,7 @@ fn main() {
let _ = otherstream.write_all(&bottle);
}
}
connectors.push_back((port,listener,connections));
connectors.push_back((privacy,port,listener,connections));
}
if let Some(threadaddress) = brokensources.pop_front() {