completely overhauled everything
This commit is contained in:
parent
1fd921ca2d
commit
af013f5891
|
@ -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"
|
|
@ -1,9 +0,0 @@
|
|||
[package]
|
||||
name = "epistlebox"
|
||||
version = "0.1.0"
|
||||
authors = ["Ellie D. <sporillium@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
time = "0.1.42"
|
||||
|
80
README.md
80
README.md
|
@ -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.
|
||||
|
|
Binary file not shown.
|
@ -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);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
144
src/main.rs
144
src/main.rs
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue