completely overhauled everything

This commit is contained in:
Ellie D 2019-06-01 21:47:35 -05:00
parent 1fd921ca2d
commit af013f5891
6 changed files with 243 additions and 238 deletions

53
Cargo.lock generated
View File

@ -1,53 +0,0 @@
[[package]]
name = "epistlebox"
version = "0.1.0"
dependencies = [
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_syscall"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880"
"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,9 +0,0 @@
[package]
name = "epistlebox"
version = "0.1.0"
authors = ["Ellie D. <sporillium@gmail.com>"]
edition = "2018"
[dependencies]
time = "0.1.42"

View File

@ -1,48 +1,64 @@
# Epistlebox Datagram Server v0.1
# Epistlebox Datagram Server v0.2
#### Elizabeth Evelene Amelia Diode, June 2019
#### AGPL v3
---
### Introduction
Email is bad, so we decided to make something even worse.
Email is bad, so we decided to make something even worse.
This is Epistlebox, a server application for receiving and collecting messages that people send
to you. If they run their own Epistlebox instance, you can write back.
to you. If they run their own Epistlebox instance, you can write back.
It's extremely simple and extremely insecure. You shouldn't use it ~~at all~~ for anything
remotely important.
---
### Features
- Uses UDP for extra simplicity and unreliability
- So simple that you can probably use it by accident
- AGPL for some reason
- Decentralized communication platform!!!1
- Arguably federated
- You can block IP addresses, we're not monsters
- Uses TCP
- Fully multithreaded
- So simple that you can probably use it by accident
- Receives arbitrary files
- AGPL for some reason
- Decentralized communication platform!!!1
- Arguably federated
- You can block IP addresses, we're not monsters
---
### How it works
Epistlebox listens on UDP port 55555 for incoming packets. When a packet arrives, it is recorded
in a nice human-readable file, which records the time, date, origin IP address, and message
contents in both UTF-8 and hex form. The server then replies to the sender with "thanks!" to let
them know that their message was received (this is UDP, so they couldn't be sure otherwise).
Epislebox listens on any number of supplied TCP ports for incoming connections. When a connection
is established, Epistlebox records all the data sent to it, and emits a file containing that data
into the working directory. It also records the IP address from which each file originated in
`.epistleslog`. Files are not named with any scheme associated with their contents, so finding out
what they are and how to open them is left as an exercise for the user.
For organization purposes, files received are placed into directiories by port number from which
they were received.
To read your epistles, you can simply open the directory where Epistlebox runs and read through
the files named `[date].epistles`. For organization purposes, Epistlebox starts a new file at
UTC midnight each day. File accesses only occur when messages are received, so no files will be
created on days when you did not receive any epistles.
Epistlebox is a sister project of [Sunbeam](https://tildegit.org/diodelass/sunbeam), with similar
unspecified intended use cases and a similarly cursed existence. Epistlebox can complement Sunbeam
by acting as a sort of recorder-logger for Sunbeam's data streams.
---
### How to block IP addresses
These days, being able to filter the messages that are sent to you is one of the most important
freedoms in all of human civilization. Epistlebox offers two simple modes of operation to allow
this:
1. IP addresses (v4 and v6 are both fine) listed in the file `.noepistles` will always have their
connections dropped and can't send you anything.
2. If you suffix a port number argument with the letter `w`, then Epistlebox will only allow
connections on that port if they originate from an IP address listed in `.yeepistles`.
You can also designate a port as local-only, causing it to reject all connections that don't
originate from the same machine, by suffixing port arguments with `p`.
---
### How to send a message to Epistlebox
1. `nc -u [server address] 55555`
2. Type your message.
3. Press enter.
4. Wait for the server to respond with `thanks!`.
1. `nc [server address] [port]`
2. Type your message, hitting enter after each line.
3. Exit `nc` with `ctrl`-`c`.
If you want to send a file to an Epistlebox instance, you could do something like
`cat malware.jar | nc [server address] [port]`
If you want to send a text file to an Epistlebox instance, you could do something like
`cat epistle.txt > nc -u [server address] 55555`
which will send one epistle per line contained in the file.
### How to reply to an Epistlebox message
In order to receive your reply, your contact must run their own Epistlebox instance, and have an
IP address in the same scope as yours (ideally, public). The latter requirement will be
significantly easier to fulfill if both you and your contact have IPv6 addresses. If you do not,
then you will have to either make use of port forwarding or run your Epistlebox instances on
remote servers.

BIN
epistlebox Executable file

Binary file not shown.

195
epistlebox.rs Normal file
View File

@ -0,0 +1,195 @@
use std::io::prelude::*;
use std::io;
use std::collections::HashSet;
use std::fs::{File,create_dir_all,OpenOptions};
use std::path::PathBuf;
use std::net::{TcpStream,TcpListener,IpAddr};
use std::env::args;
use std::thread;
use std::process::exit;
#[derive(PartialEq)]
enum PortPrivacy {
Public,
Whitelist,
Private,
}
static BLOCKFILE_NAME:&str = ".noepistles";
static ALLOWFILE_NAME:&str = ".yeepistles";
static LOGFILE_NAME:&str = ".epistleslog";
static FILENAME_PREFIX:&str = "epistle";
static FILENAME_SUFFIX:&str = ".epistle";
fn load_rules(rulefilepath:&str) -> HashSet<IpAddr> {
let mut rules:HashSet<IpAddr> = HashSet::new();
match File::open(rulefilepath) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => (),
_ => {
eprintln!("-!- failed to open {} to read IP address ruleing rules: {}",rulefilepath,why);
exit(1);
},
},
Ok(mut file) => {
let mut rulestring = String::new();
match file.read_to_string(&mut rulestring) {
Err(why) => {
eprintln!("-!- failed to read {} for IP address ruleing rules: {}",rulefilepath,why);
exit(1);
},
Ok(_) => (),
};
for line in rulestring.lines() {
match line.parse::<IpAddr>() {
Err(_why) => {
eprintln!("-!- could not parse '{}' in {} as an IP address",line,rulefilepath);
exit(1);
},
Ok(address) => rules.insert(address),
};
}
},
};
return rules;
}
fn record(mut stream:TcpStream,filename:&str) {
let mut bottle:[u8;512] = [0x00;512];
let mut outfile = match File::create(filename) {
Err(why) => {
eprintln!("-!- failed to open file {} for writing: {}",filename,why);
return;
},
Ok(f) => f,
};
loop {
match stream.read(&mut bottle) {
Err(why) => match why.kind() {
io::ErrorKind::WouldBlock => (),
io::ErrorKind::Interrupted => (),
_ => {
eprintln!("-!- failed to collect data for writing to {}: {}",filename,why);
return;
},
},
Ok(n) => match n {
0 => {
println!("-i- finished receiving {}",filename);
return;
},
_ => match outfile.write_all(&bottle[..n]) {
Err(why) => {
eprintln!("-!- failed to write to {}: {}",filename,why);
return;
},
Ok(_) => (),
},
},
};
}
}
fn main() {
let argv:Vec<String> = args().collect();
if argv.len() < 2 {
eprintln!("usage: epistlebox [port number]...");
exit(1);
}
let mut ports:Vec<(PortPrivacy,u16)> = Vec::new();
for arg in argv[1..].iter() {
match arg.trim_end_matches(&['w','p'][..]).parse() {
Err(_why) => {
eprintln!("-!- could not parse '{}' as a port number",arg);
},
Ok(n) => match arg.chars().last() {
Some('w') => ports.push((PortPrivacy::Whitelist,n)),
Some('p') => ports.push((PortPrivacy::Private,n)),
_ => ports.push((PortPrivacy::Public,n)),
},
};
}
let mut listeners:Vec<(PortPrivacy,u16,TcpListener)> = Vec::new();
while let Some((privacy,port)) = ports.pop() {
let bindaddress = match privacy {
PortPrivacy::Private => "127.0.0.1",
_ => "[::]",
};
match TcpListener::bind(&format!("{}:{}",bindaddress,port)) {
Err(why) => {
eprintln!("-!- could not bind listener to port {}: {}",port,why);
},
Ok(listener) => {
listener.set_nonblocking(true).expect("cannot set listener to nonblocking");
listeners.push((privacy,port,listener));
},
};
}
let blocklist = load_rules(BLOCKFILE_NAME);
let allowlist = load_rules(ALLOWFILE_NAME);
loop {
for (privacy,port,listener) in listeners.iter() {
match listener.accept() {
Err(why) => match why.kind() {
io::ErrorKind::WouldBlock => (),
_ => eprintln!("-!- failed to accept connection on port {}: {}",port,why),
},
Ok((stream,address)) => {
if blocklist.contains(&address.ip()) {
eprintln!("-x- connection refused for {} (address found in {})",address,BLOCKFILE_NAME);
continue;
}
if privacy == &PortPrivacy::Whitelist && !allowlist.contains(&address.ip()) {
eprintln!("-x- connection refused for {} (address not found in {})",address,ALLOWFILE_NAME);
continue;
}
let mut filename = format!("./{}{}",FILENAME_PREFIX,FILENAME_SUFFIX);
let mut filenumber = 1;
while PathBuf::from(&filename).exists() {
filename = format!("{}/{}{}{}",port,FILENAME_PREFIX,filenumber,FILENAME_SUFFIX);
filenumber += 1;
}
println!("-");
match create_dir_all(&format!("./{}",port)) {
Err(why) => {
eprintln!("-!- failed to access directiory ./{}: {}",port,why);
},
Ok(_) => (),
};
let threadfilename = filename.clone();
let logstring = format!("{}: {} via port {}",filename,address,port);
let mut logfile = match OpenOptions::new().append(true).open(&LOGFILE_NAME) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => match File::create(&LOGFILE_NAME) {
Err(why) => {
eprintln!("-!- failed to create logfile {}: {}",LOGFILE_NAME,why);
None
},
Ok(file) => Some(file),
},
_ => {
eprintln!("-!- failed to open logfile {}: {}",LOGFILE_NAME,why);
None
},
}
Ok(file) => Some(file),
};
if let Some(mut file) = logfile {
match file.write_all(&logstring.as_bytes()) {
Err(why) => eprintln!("-!- failed to write logfile {}: {}",LOGFILE_NAME,why),
Ok(_) => (),
};
}
thread::spawn(move || {
record(stream,&threadfilename);
});
},
};
}
}
}

View File

@ -1,144 +0,0 @@
extern crate time;
use time::{Tm,now_utc,strftime};
use std::io::prelude::*;
use std::io;
use std::collections::HashSet;
use std::net::{UdpSocket,IpAddr};
use std::fs::{File,OpenOptions};
use std::time::Duration;
use std::thread::sleep;
static PORT:u16 = 55555;
static BLOCKS_FILE:&str = "rejectepistles";
static ACK_MESSAGE:&str = "thanks!\n";
fn print_time(now:&Tm) -> String {
let mut micros:String = format!("{}",now.tm_nsec/1_000);
while micros.len() < 6 {
micros = format!("0{}",micros);
}
return format!("{}.{}Z",strftime("%Y-%m-%dT%H:%M:%S",&now).expect("bad strftime in print_time()"),micros);
}
fn print_date(now:&Tm) -> String {
return strftime("%Y-%m-%d",&now).expect("bad strftime in print_date()");
}
fn bytes_to_hex(v:&[u8]) -> String {
let mut result:String = String::from("");
for x in 0..v.len() {
if v[x] == 0x00 {
result.push_str(&format!("00"));
} else if v[x] < 0x10 {
result.push_str(&format!("0{:x}",v[x]));
} else {
result.push_str(&format!("{:x}",v[x]));
}
if x < v.len()-1 {
result.push_str(".");
}
}
return result;
}
fn main() {
'recovery:loop {
// load the blocked addresses from the file, if present
let mut rejectaddresses:HashSet<IpAddr> = HashSet::new();
match File::open(&BLOCKS_FILE) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => (),
_ => {
eprintln!("could not open file {}: {}",BLOCKS_FILE,why);
},
},
Ok(mut blocksfile) => {
let mut blockstring = String::new();
match blocksfile.read_to_string(&mut blockstring) {
Err(why) => {
eprintln!("could not read file {}: {}",BLOCKS_FILE,why);
},
Ok(_) => for line in blockstring.lines() {
let realline = line.splitn(2,"#").collect::<Vec<&str>>()[0];
match realline.parse::<IpAddr>() {
Err(_why) => {
eprintln!("{}: could not parse `{}` as an IP address",BLOCKS_FILE,line);
},
Ok(addr) => {
rejectaddresses.insert(addr);
},
};
},
};
},
};
// open and bind the socket
let socket = match UdpSocket::bind(&format!("[::]:{}",PORT)) {
Err(why) => {
eprintln!("failed to bind to local address on port {}: {}",PORT,why);
sleep(Duration::from_millis(5000));
continue 'recovery;
},
Ok(s) => s,
};
// accept packets up to 64k long (UDP packets longer than 508 characters will probably get dropped though)
let mut bottle:[u8;65535] = [0x00;65535];
'processor:loop {
// each time we get a packet, print out the contents and write them to a file too.
match socket.recv_from(&mut bottle) {
Err(why) => {
eprintln!("failed to receive epistles: {}",why);
break 'processor;
},
Ok((recvlen,srcaddr)) => {
let now = now_utc();
if rejectaddresses.contains(&srcaddr.ip()) {
continue 'processor;
}
let epistle = bottle[..recvlen].to_vec();
let hex = bytes_to_hex(&epistle);
let filename = format!("{}.epistles",print_date(&now));
let mut eventstring = String::new();
eventstring.push_str(&format!("{}\n",print_time(&now)));
eventstring.push_str(&format!("{}\n",srcaddr));
eventstring.push_str(&format!("{}\n",String::from_utf8_lossy(&epistle).trim()));
eventstring.push_str(&format!("{}\n\n",hex));
print!("{}",eventstring);
let mut epistlefile = match OpenOptions::new().append(true).open(&filename) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => match File::create(&filename) {
Err(why) => {
eprintln!("failed to create file {}: {}",filename,why);
continue 'processor;
},
Ok(f) => f,
},
_ => {
eprintln!("failed to open file {}: {}",filename,why);
continue 'processor;
}
},
Ok(f) => f,
};
match epistlefile.write_all(&eventstring.as_bytes()) {
Err(why) => {
eprintln!("failed to write to file {}: {}",filename,why);
},
Ok(_) => (),
};
match socket.send_to(&ACK_MESSAGE.as_bytes(),&srcaddr) {
Err(why) => {
eprintln!("failed to send acknowledgement to sender at {}: {}",srcaddr,why);
},
Ok(_) => (),
};
},
};
} // 'processor
sleep(Duration::from_millis(5000));
} // 'recovery
}