mirror of
https://github.com/MeexReay/sRAC.git
synced 2025-06-24 02:22:57 +03:00
refactorization
This commit is contained in:
parent
8dfb087e78
commit
2b4d1a4e27
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
||||||
|
}
|
91
Cargo.lock
generated
91
Cargo.lock
generated
@ -117,7 +117,7 @@ version = "0.1.2+2.0"
|
|||||||
source = "git+https://github.com/MeexReay/bRAC.git?tag=0.1.2%2B2.0#52720c2748c3153f5ada996cc2b32366a9397549"
|
source = "git+https://github.com/MeexReay/bRAC.git?tag=0.1.2%2B2.0#52720c2748c3153f5ada996cc2b32366a9397549"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored 3.0.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
@ -275,12 +275,33 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colog"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c426b7af8d5e0ad79de6713996632ce31f0d68ba84068fb0d654b396e519df0"
|
||||||
|
dependencies = [
|
||||||
|
"colored 2.2.0",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@ -343,6 +364,29 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -503,6 +547,30 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.33"
|
version = "0.1.33"
|
||||||
@ -620,6 +688,21 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@ -807,6 +890,8 @@ dependencies = [
|
|||||||
"bRAC",
|
"bRAC",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colog",
|
||||||
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
"rand",
|
"rand",
|
||||||
"rustls",
|
"rustls",
|
||||||
@ -910,9 +995,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.26.2"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
|
@ -10,4 +10,6 @@ md-5 = "0.10.6"
|
|||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
clap = { version = "4.5.36", features = ["derive"] }
|
clap = { version = "4.5.36", features = ["derive"] }
|
||||||
rustls = "0.23.25"
|
rustls = "0.23.25"
|
||||||
tungstenite = "0.26.2"
|
tungstenite = "0.27.0"
|
||||||
|
colog = "1.3.0"
|
||||||
|
log = "0.4.27"
|
||||||
|
@ -85,6 +85,8 @@ Server sends:
|
|||||||
|
|
||||||
Default port - 42667
|
Default port - 42667
|
||||||
|
|
||||||
|
The same as RAC, but wrapped with TLS
|
||||||
|
|
||||||
# WRAC Protocol
|
# WRAC Protocol
|
||||||
|
|
||||||
Default port - 52666
|
Default port - 52666
|
||||||
@ -129,3 +131,5 @@ Server sends:
|
|||||||
# WRACS Protocol
|
# WRACS Protocol
|
||||||
|
|
||||||
Default port - 52667
|
Default port - 52667
|
||||||
|
|
||||||
|
Just the same WRAC protocol but wrapped with TLS, like RACS
|
273
src/ctx.rs
Normal file
273
src/ctx.rs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
error::Error,
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::{Cursor, Read, Write},
|
||||||
|
net::IpAddr,
|
||||||
|
sync::{
|
||||||
|
Arc, RwLock,
|
||||||
|
atomic::{AtomicU64, Ordering},
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bRAC::{chat::format_message, util::sanitize_text};
|
||||||
|
use chrono::{DateTime, Local, TimeZone};
|
||||||
|
use log::info;
|
||||||
|
use md5::{Digest, Md5};
|
||||||
|
use rand::{Rng, distr::Alphanumeric};
|
||||||
|
|
||||||
|
use crate::{Args, load_accounts, load_messages};
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
pub args: Arc<Args>,
|
||||||
|
pub messages_file: Option<String>,
|
||||||
|
pub accounts_file: Option<String>,
|
||||||
|
pub messages: RwLock<Vec<u8>>,
|
||||||
|
pub accounts: RwLock<Vec<Account>>,
|
||||||
|
pub messages_offset: AtomicU64,
|
||||||
|
pub notifications: RwLock<HashMap<u32, Vec<u8>>>,
|
||||||
|
pub timeouts: RwLock<HashMap<u32, Duration>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn new(
|
||||||
|
args: Arc<Args>,
|
||||||
|
messages_file: Option<String>,
|
||||||
|
accounts_file: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
args,
|
||||||
|
messages_file: messages_file.clone(),
|
||||||
|
accounts_file: accounts_file.clone(),
|
||||||
|
messages: RwLock::new(load_messages(messages_file.clone())),
|
||||||
|
accounts: RwLock::new(load_accounts(accounts_file.clone())),
|
||||||
|
timeouts: RwLock::new(HashMap::new()),
|
||||||
|
messages_offset: AtomicU64::default(),
|
||||||
|
notifications: RwLock::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_message(&self, msg: Vec<u8>) {
|
||||||
|
if let Some(messages_file) = self.messages_file.clone() {
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(messages_file)
|
||||||
|
.expect("error messages file open");
|
||||||
|
|
||||||
|
file.write_all(&msg).expect("error messages file write");
|
||||||
|
file.flush().expect("error messages file flush");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.messages.write().unwrap().append(&mut msg.clone());
|
||||||
|
|
||||||
|
let content = self.messages.read().unwrap().clone();
|
||||||
|
|
||||||
|
if content.len() > self.args.messages_total_limit {
|
||||||
|
let offset = content.len() - self.args.messages_total_limit;
|
||||||
|
*self.messages.write().unwrap() = content[offset..].to_vec();
|
||||||
|
self.messages_offset.store(offset as u64, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
|
||||||
|
for acc in self.accounts.read().unwrap().iter().rev() {
|
||||||
|
if acc.addr() == addr {
|
||||||
|
return Some(acc.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_account(&self, name: &str) -> Option<Account> {
|
||||||
|
for acc in self.accounts.read().unwrap().iter() {
|
||||||
|
if acc.name() == name {
|
||||||
|
return Some(acc.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_account(&self, acc: Account) {
|
||||||
|
if let Some(accounts_file) = self.accounts_file.clone() {
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(accounts_file)
|
||||||
|
.expect("error accounts file open");
|
||||||
|
|
||||||
|
file.write_all(&acc.to_bytes())
|
||||||
|
.expect("error accounts file write");
|
||||||
|
file.write_all(b"\n").expect("error accounts file write");
|
||||||
|
file.flush().expect("error accounts file flush");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accounts.write().unwrap().push(acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Account {
|
||||||
|
name: String,
|
||||||
|
pass: Vec<u8>,
|
||||||
|
salt: String,
|
||||||
|
addr: String,
|
||||||
|
date: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn password_hash(name: &str, pass: &str, salt: &str) -> Vec<u8> {
|
||||||
|
let mut hasher = Md5::new();
|
||||||
|
hasher.update(format!("{name}{pass}{salt}").as_bytes());
|
||||||
|
hasher.finalize().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn password_salt() -> String {
|
||||||
|
rand::rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(16)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Account {
|
||||||
|
pub fn new(name: String, password: String, addr: String, date: i64) -> Self {
|
||||||
|
let salt = password_salt();
|
||||||
|
|
||||||
|
Account {
|
||||||
|
pass: password_hash(&name, &password, &salt),
|
||||||
|
name: name.clone(),
|
||||||
|
salt: salt.clone(),
|
||||||
|
addr,
|
||||||
|
date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_password(&self, password: &str) -> bool {
|
||||||
|
password_hash(&self.name, password, &self.salt) == self.pass
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addr(&self) -> &str {
|
||||||
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn date(&self) -> i64 {
|
||||||
|
self.date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.append(&mut (self.name.len() as u32).to_le_bytes().to_vec());
|
||||||
|
data.append(&mut (self.salt.len() as u32).to_le_bytes().to_vec());
|
||||||
|
data.append(&mut (self.addr.len() as u32).to_le_bytes().to_vec());
|
||||||
|
data.append(&mut (self.pass.len() as u32).to_le_bytes().to_vec());
|
||||||
|
data.append(&mut self.name.as_bytes().to_vec());
|
||||||
|
data.append(&mut self.salt.as_bytes().to_vec());
|
||||||
|
data.append(&mut self.addr.as_bytes().to_vec());
|
||||||
|
data.append(&mut self.pass.clone());
|
||||||
|
data.append(&mut self.date.to_le_bytes().to_vec());
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(text: Vec<u8>) -> Self {
|
||||||
|
let mut text = Cursor::new(text);
|
||||||
|
|
||||||
|
let mut name_len = [0; 4];
|
||||||
|
text.read_exact(&mut name_len).unwrap();
|
||||||
|
let name_len = u32::from_le_bytes(name_len) as usize;
|
||||||
|
|
||||||
|
let mut salt_len = [0; 4];
|
||||||
|
text.read_exact(&mut salt_len).unwrap();
|
||||||
|
let salt_len = u32::from_le_bytes(salt_len) as usize;
|
||||||
|
|
||||||
|
let mut addr_len = [0; 4];
|
||||||
|
text.read_exact(&mut addr_len).unwrap();
|
||||||
|
let addr_len = u32::from_le_bytes(addr_len) as usize;
|
||||||
|
|
||||||
|
let mut pass_len = [0; 4];
|
||||||
|
text.read_exact(&mut pass_len).unwrap();
|
||||||
|
let pass_len = u32::from_le_bytes(pass_len) as usize;
|
||||||
|
|
||||||
|
let mut name = vec![0; name_len];
|
||||||
|
text.read_exact(&mut name).unwrap();
|
||||||
|
let name = String::from_utf8_lossy(&name).to_string();
|
||||||
|
|
||||||
|
let mut salt = vec![0; salt_len];
|
||||||
|
text.read_exact(&mut salt).unwrap();
|
||||||
|
let salt = String::from_utf8_lossy(&salt).to_string();
|
||||||
|
|
||||||
|
let mut addr = vec![0; addr_len];
|
||||||
|
text.read_exact(&mut addr).unwrap();
|
||||||
|
let addr = String::from_utf8_lossy(&addr).to_string();
|
||||||
|
|
||||||
|
let mut pass = vec![0; pass_len];
|
||||||
|
text.read_exact(&mut pass).unwrap();
|
||||||
|
|
||||||
|
let mut date = [0; 8];
|
||||||
|
text.read_exact(&mut date).unwrap();
|
||||||
|
let date = i64::from_le_bytes(date);
|
||||||
|
|
||||||
|
Account {
|
||||||
|
name,
|
||||||
|
salt,
|
||||||
|
pass,
|
||||||
|
addr,
|
||||||
|
date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_prefix(time_millis: i64, address: Option<String>) -> String {
|
||||||
|
let datetime: DateTime<Local> = Local.timestamp_millis_opt(time_millis).unwrap();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"[{}]{} ",
|
||||||
|
datetime.format("%d.%m.%Y %H:%M"),
|
||||||
|
if let Some(addr) = address {
|
||||||
|
format!(" {{{addr}}}")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_message(
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
context: Arc<Context>,
|
||||||
|
addr: Option<IpAddr>,
|
||||||
|
sanitize: bool,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
|
||||||
|
msg.append(
|
||||||
|
&mut message_prefix(Local::now().timestamp_millis(), addr.map(|o| o.to_string()))
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if sanitize {
|
||||||
|
msg.append(
|
||||||
|
&mut sanitize_text(&String::from_utf8_lossy(&buf.clone()))
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
msg.append(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = format_message(addr.is_some(), String::from_utf8_lossy(&msg).to_string()) {
|
||||||
|
info!("{}", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.push(b'\n');
|
||||||
|
|
||||||
|
context.push_message(msg);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
135
src/logic.rs
Normal file
135
src/logic.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{Arc, atomic::Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::ctx::{Account, Context, add_message};
|
||||||
|
|
||||||
|
pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Error>> {
|
||||||
|
let messages_len = ctx.messages.read().unwrap().len() as u64;
|
||||||
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
|
|
||||||
|
if let Some(splash) = &ctx.args.splash {
|
||||||
|
Ok(messages_len + splash.len() as u64 + offset)
|
||||||
|
} else {
|
||||||
|
Ok(messages_len + offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_total_data(
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
_: SocketAddr,
|
||||||
|
_: Option<u64>, // sent_size
|
||||||
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut messages = ctx.messages.read().unwrap().clone();
|
||||||
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
|
|
||||||
|
let mut messages = if offset > 0 {
|
||||||
|
let mut buf = vec![0; offset as usize];
|
||||||
|
buf.append(&mut messages);
|
||||||
|
buf
|
||||||
|
} else {
|
||||||
|
messages
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(splash) = &ctx.args.splash {
|
||||||
|
messages.append(&mut splash.clone().as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_chunked_data(
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
_: SocketAddr,
|
||||||
|
_: Option<u64>, // sent_size
|
||||||
|
client_has: u64,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let messages = ctx.messages.read().unwrap().clone();
|
||||||
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
|
let client_has = if let Some(splash) = &ctx.args.splash {
|
||||||
|
client_has - splash.len() as u64
|
||||||
|
} else {
|
||||||
|
client_has
|
||||||
|
};
|
||||||
|
|
||||||
|
if client_has <= offset {
|
||||||
|
Ok(messages)
|
||||||
|
} else {
|
||||||
|
let client_has = (client_has - offset) as usize;
|
||||||
|
Ok(messages[client_has..].to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_send_message(
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
message: Vec<u8>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
if !ctx.args.auth_only {
|
||||||
|
add_message(
|
||||||
|
&mut message.clone(),
|
||||||
|
ctx.clone(),
|
||||||
|
Some(addr.ip()),
|
||||||
|
ctx.args.sanitize,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_send_auth_message(
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
_: SocketAddr,
|
||||||
|
name: &str,
|
||||||
|
password: &str,
|
||||||
|
text: &str,
|
||||||
|
) -> Result<Option<u8>, Box<dyn Error>> {
|
||||||
|
if let Some(acc) = ctx.get_account(name) {
|
||||||
|
if acc.check_password(password) {
|
||||||
|
add_message(
|
||||||
|
&mut text.as_bytes().to_vec(),
|
||||||
|
ctx.clone(),
|
||||||
|
None,
|
||||||
|
ctx.args.sanitize,
|
||||||
|
)?;
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(0x02))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Some(0x01))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_register_user(
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
name: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Option<u8>, Box<dyn Error>> {
|
||||||
|
let addr = addr.ip().to_string();
|
||||||
|
|
||||||
|
let now: i64 = Local::now().timestamp_millis();
|
||||||
|
|
||||||
|
if ctx.get_account(name).is_some()
|
||||||
|
|| (if let Some(acc) = ctx.get_account_by_addr(&addr) {
|
||||||
|
((now - acc.date()) as usize) < 1000 * ctx.args.register_timeout
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Ok(Some(0x01));
|
||||||
|
}
|
||||||
|
|
||||||
|
let account = Account::new(name.to_string(), password.to_string(), addr, now);
|
||||||
|
|
||||||
|
info!("user registered: {name}");
|
||||||
|
|
||||||
|
ctx.push_account(account);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
577
src/main.rs
577
src/main.rs
@ -1,28 +1,30 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
fs::{self, OpenOptions},
|
fs,
|
||||||
io::{Cursor, Read, Write},
|
io::{Read, Write},
|
||||||
net::{IpAddr, SocketAddr, TcpListener},
|
net::{SocketAddr, TcpListener},
|
||||||
sync::{
|
sync::Arc,
|
||||||
Arc, RwLock,
|
|
||||||
atomic::{AtomicUsize, Ordering},
|
|
||||||
},
|
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bRAC::{chat::format_message, util::sanitize_text};
|
use log::{debug, info};
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
|
||||||
use md5::{Digest, Md5};
|
|
||||||
use rand::{Rng, distr::Alphanumeric};
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rustls::{
|
use rustls::{
|
||||||
ServerConfig, ServerConnection, StreamOwned,
|
ServerConfig, ServerConnection, StreamOwned,
|
||||||
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
|
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
|
||||||
};
|
};
|
||||||
use tungstenite::{Bytes, Message, accept};
|
|
||||||
|
use crate::{
|
||||||
|
ctx::{Account, Context},
|
||||||
|
rac::accept_rac_stream,
|
||||||
|
wrac::accept_wrac_stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod ctx;
|
||||||
|
mod logic;
|
||||||
|
mod rac;
|
||||||
|
mod wrac;
|
||||||
|
|
||||||
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
|
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
|
||||||
if let Some(accounts_file) = accounts_file.clone() {
|
if let Some(accounts_file) = accounts_file.clone() {
|
||||||
@ -53,545 +55,6 @@ fn load_messages(messages_file: Option<String>) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Context {
|
|
||||||
args: Arc<Args>,
|
|
||||||
messages_file: Option<String>,
|
|
||||||
accounts_file: Option<String>,
|
|
||||||
messages: RwLock<Vec<u8>>,
|
|
||||||
accounts: RwLock<Vec<Account>>,
|
|
||||||
messages_offset: AtomicUsize,
|
|
||||||
notifications: RwLock<HashMap<u32, Vec<u8>>>,
|
|
||||||
timeouts: RwLock<HashMap<u32, Duration>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
fn new(args: Arc<Args>, messages_file: Option<String>, accounts_file: Option<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
args,
|
|
||||||
messages_file: messages_file.clone(),
|
|
||||||
accounts_file: accounts_file.clone(),
|
|
||||||
messages: RwLock::new(load_messages(messages_file.clone())),
|
|
||||||
accounts: RwLock::new(load_accounts(accounts_file.clone())),
|
|
||||||
timeouts: RwLock::new(HashMap::new()),
|
|
||||||
messages_offset: AtomicUsize::default(),
|
|
||||||
notifications: RwLock::new(HashMap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_message(&self, msg: Vec<u8>) {
|
|
||||||
if let Some(messages_file) = self.messages_file.clone() {
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.append(true)
|
|
||||||
.create(true)
|
|
||||||
.open(messages_file)
|
|
||||||
.expect("error messages file open");
|
|
||||||
|
|
||||||
file.write_all(&msg).expect("error messages file write");
|
|
||||||
file.flush().expect("error messages file flush");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.messages.write().unwrap().append(&mut msg.clone());
|
|
||||||
|
|
||||||
let content = self.messages.read().unwrap().clone();
|
|
||||||
|
|
||||||
if content.len() > self.args.messages_total_limit {
|
|
||||||
let offset = content.len() - self.args.messages_total_limit;
|
|
||||||
*self.messages.write().unwrap() = content[offset..].to_vec();
|
|
||||||
self.messages_offset.store(offset, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
|
|
||||||
for acc in self.accounts.read().unwrap().iter().rev() {
|
|
||||||
if acc.addr() == addr {
|
|
||||||
return Some(acc.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_account(&self, name: &str) -> Option<Account> {
|
|
||||||
for acc in self.accounts.read().unwrap().iter() {
|
|
||||||
if acc.name() == name {
|
|
||||||
return Some(acc.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_account(&self, acc: Account) {
|
|
||||||
if let Some(accounts_file) = self.accounts_file.clone() {
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.append(true)
|
|
||||||
.create(true)
|
|
||||||
.open(accounts_file)
|
|
||||||
.expect("error accounts file open");
|
|
||||||
|
|
||||||
file.write_all(&acc.to_bytes())
|
|
||||||
.expect("error accounts file write");
|
|
||||||
file.write_all(b"\n").expect("error accounts file write");
|
|
||||||
file.flush().expect("error accounts file flush");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.accounts.write().unwrap().push(acc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Account {
|
|
||||||
name: String,
|
|
||||||
pass: Vec<u8>,
|
|
||||||
salt: String,
|
|
||||||
addr: String,
|
|
||||||
date: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password_hash(name: &str, pass: &str, salt: &str) -> Vec<u8> {
|
|
||||||
let mut hasher = Md5::new();
|
|
||||||
hasher.update(format!("{name}{pass}{salt}").as_bytes());
|
|
||||||
hasher.finalize().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password_salt() -> String {
|
|
||||||
rand::rng()
|
|
||||||
.sample_iter(&Alphanumeric)
|
|
||||||
.take(16)
|
|
||||||
.map(char::from)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Account {
|
|
||||||
pub fn new(name: String, password: String, addr: String, date: i64) -> Self {
|
|
||||||
let salt = password_salt();
|
|
||||||
|
|
||||||
Account {
|
|
||||||
pass: password_hash(&name, &password, &salt),
|
|
||||||
name: name.clone(),
|
|
||||||
salt: salt.clone(),
|
|
||||||
addr,
|
|
||||||
date,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_password(&self, password: &str) -> bool {
|
|
||||||
password_hash(&self.name, password, &self.salt) == self.pass
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr(&self) -> &str {
|
|
||||||
&self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn date(&self) -> i64 {
|
|
||||||
self.date
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let mut data = Vec::new();
|
|
||||||
data.append(&mut (self.name.len() as u32).to_le_bytes().to_vec());
|
|
||||||
data.append(&mut (self.salt.len() as u32).to_le_bytes().to_vec());
|
|
||||||
data.append(&mut (self.addr.len() as u32).to_le_bytes().to_vec());
|
|
||||||
data.append(&mut (self.pass.len() as u32).to_le_bytes().to_vec());
|
|
||||||
data.append(&mut self.name.as_bytes().to_vec());
|
|
||||||
data.append(&mut self.salt.as_bytes().to_vec());
|
|
||||||
data.append(&mut self.addr.as_bytes().to_vec());
|
|
||||||
data.append(&mut self.pass.clone());
|
|
||||||
data.append(&mut self.date.to_le_bytes().to_vec());
|
|
||||||
data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_bytes(text: Vec<u8>) -> Self {
|
|
||||||
let mut text = Cursor::new(text);
|
|
||||||
|
|
||||||
let mut name_len = [0; 4];
|
|
||||||
text.read_exact(&mut name_len).unwrap();
|
|
||||||
let name_len = u32::from_le_bytes(name_len) as usize;
|
|
||||||
|
|
||||||
let mut salt_len = [0; 4];
|
|
||||||
text.read_exact(&mut salt_len).unwrap();
|
|
||||||
let salt_len = u32::from_le_bytes(salt_len) as usize;
|
|
||||||
|
|
||||||
let mut addr_len = [0; 4];
|
|
||||||
text.read_exact(&mut addr_len).unwrap();
|
|
||||||
let addr_len = u32::from_le_bytes(addr_len) as usize;
|
|
||||||
|
|
||||||
let mut pass_len = [0; 4];
|
|
||||||
text.read_exact(&mut pass_len).unwrap();
|
|
||||||
let pass_len = u32::from_le_bytes(pass_len) as usize;
|
|
||||||
|
|
||||||
let mut name = vec![0; name_len];
|
|
||||||
text.read_exact(&mut name).unwrap();
|
|
||||||
let name = String::from_utf8_lossy(&name).to_string();
|
|
||||||
|
|
||||||
let mut salt = vec![0; salt_len];
|
|
||||||
text.read_exact(&mut salt).unwrap();
|
|
||||||
let salt = String::from_utf8_lossy(&salt).to_string();
|
|
||||||
|
|
||||||
let mut addr = vec![0; addr_len];
|
|
||||||
text.read_exact(&mut addr).unwrap();
|
|
||||||
let addr = String::from_utf8_lossy(&addr).to_string();
|
|
||||||
|
|
||||||
let mut pass = vec![0; pass_len];
|
|
||||||
text.read_exact(&mut pass).unwrap();
|
|
||||||
|
|
||||||
let mut date = [0; 8];
|
|
||||||
text.read_exact(&mut date).unwrap();
|
|
||||||
let date = i64::from_le_bytes(date);
|
|
||||||
|
|
||||||
Account {
|
|
||||||
name,
|
|
||||||
salt,
|
|
||||||
pass,
|
|
||||||
addr,
|
|
||||||
date,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message_prefix(time_millis: i64, address: Option<String>) -> String {
|
|
||||||
let datetime: DateTime<Local> = Local.timestamp_millis_opt(time_millis).unwrap();
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"[{}]{} ",
|
|
||||||
datetime.format("%d.%m.%Y %H:%M"),
|
|
||||||
if let Some(addr) = address {
|
|
||||||
format!(" {{{addr}}}")
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_message(
|
|
||||||
buf: &mut Vec<u8>,
|
|
||||||
context: Arc<Context>,
|
|
||||||
addr: Option<IpAddr>,
|
|
||||||
sanitize: bool,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut msg = Vec::new();
|
|
||||||
|
|
||||||
msg.append(
|
|
||||||
&mut message_prefix(Local::now().timestamp_millis(), addr.map(|o| o.to_string()))
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if sanitize {
|
|
||||||
msg.append(
|
|
||||||
&mut sanitize_text(&String::from_utf8_lossy(&buf.clone()))
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
msg.append(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(msg) = format_message(addr.is_some(), String::from_utf8_lossy(&msg).to_string()) {
|
|
||||||
println!("{}", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.push(b'\n');
|
|
||||||
|
|
||||||
context.push_message(msg);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept_wrac_stream(
|
|
||||||
stream: impl Read + Write,
|
|
||||||
addr: SocketAddr,
|
|
||||||
ctx: Arc<Context>,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut websocket = match accept(stream) {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => return Err(format!("accept websocket error: {}", e).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Ok(msg) = websocket.read() {
|
|
||||||
if let Some(data) = match msg {
|
|
||||||
Message::Binary(o) => Some(o.to_vec()),
|
|
||||||
Message::Text(o) => Some(o.as_bytes().to_vec()),
|
|
||||||
Message::Close(_) => return Ok(()),
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
let mut data = data;
|
|
||||||
let Some(id) = data.drain(..1).next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if id == 0x00 {
|
|
||||||
let messages = ctx.messages.read().unwrap().clone();
|
|
||||||
|
|
||||||
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
|
||||||
|
|
||||||
let mut messages = if offset > 0 {
|
|
||||||
let mut buf = vec![0; offset];
|
|
||||||
buf.append(&mut messages.clone());
|
|
||||||
buf
|
|
||||||
} else {
|
|
||||||
messages
|
|
||||||
};
|
|
||||||
|
|
||||||
if data.is_empty() {
|
|
||||||
if let Some(splash) = &ctx.args.splash {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(
|
|
||||||
(messages.len() + splash.len() + offset)
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
)))?;
|
|
||||||
} else {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(
|
|
||||||
(messages.len() + offset).to_string().as_bytes().to_vec(),
|
|
||||||
)))?;
|
|
||||||
}
|
|
||||||
websocket.flush()?;
|
|
||||||
} else {
|
|
||||||
let Some(id) = data.drain(..1).next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if id == 0x01 {
|
|
||||||
if let Some(splash) = &ctx.args.splash {
|
|
||||||
messages.append(&mut splash.clone().as_bytes().to_vec());
|
|
||||||
}
|
|
||||||
websocket.write(Message::Binary(Bytes::from(messages)))?;
|
|
||||||
websocket.flush()?;
|
|
||||||
} else if id == 0x02 {
|
|
||||||
let last_size: usize = String::from_utf8(data)?.parse()?;
|
|
||||||
if let Some(splash) = &ctx.args.splash {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(
|
|
||||||
messages[(last_size - splash.len())..].to_vec(),
|
|
||||||
)))?;
|
|
||||||
} else {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(
|
|
||||||
messages[last_size..].to_vec(),
|
|
||||||
)))?;
|
|
||||||
}
|
|
||||||
websocket.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if id == 0x01 {
|
|
||||||
if !ctx.args.auth_only {
|
|
||||||
add_message(&mut data, ctx.clone(), Some(addr.ip()), ctx.args.sanitize)?;
|
|
||||||
}
|
|
||||||
} else if id == 0x02 {
|
|
||||||
let msg = String::from_utf8_lossy(&data).to_string();
|
|
||||||
|
|
||||||
let mut segments = msg.split("\n");
|
|
||||||
|
|
||||||
let Some(name) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(password) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(text) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(acc) = ctx.get_account(name) {
|
|
||||||
if acc.check_password(password) {
|
|
||||||
add_message(
|
|
||||||
&mut text.as_bytes().to_vec(),
|
|
||||||
ctx.clone(),
|
|
||||||
None,
|
|
||||||
ctx.args.sanitize,
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(vec![0x02])))?;
|
|
||||||
websocket.flush()?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
websocket.write(Message::Binary(Bytes::from(vec![0x01])))?;
|
|
||||||
websocket.flush()?;
|
|
||||||
}
|
|
||||||
} else if id == 0x03 {
|
|
||||||
let msg = String::from_utf8_lossy(&data).to_string();
|
|
||||||
|
|
||||||
let mut segments = msg.split("\n");
|
|
||||||
|
|
||||||
let Some(name) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(password) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let addr = addr.ip().to_string();
|
|
||||||
|
|
||||||
let now: i64 = Local::now().timestamp_millis();
|
|
||||||
|
|
||||||
if ctx.get_account(name).is_some()
|
|
||||||
|| (if let Some(acc) = ctx.get_account_by_addr(&addr) {
|
|
||||||
((now - acc.date()) as usize) < 1000 * ctx.args.register_timeout
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
})
|
|
||||||
{
|
|
||||||
websocket.write(Message::Binary(Bytes::from(vec![0x01])))?;
|
|
||||||
websocket.flush()?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let account = Account::new(name.to_string(), password.to_string(), addr, now);
|
|
||||||
|
|
||||||
println!("user registered: {name}");
|
|
||||||
|
|
||||||
ctx.push_account(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept_rac_stream(
|
|
||||||
mut stream: impl Read + Write,
|
|
||||||
addr: SocketAddr,
|
|
||||||
ctx: Arc<Context>,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut buf = vec![0];
|
|
||||||
stream.read_exact(&mut buf)?;
|
|
||||||
|
|
||||||
if buf[0] == 0x00 {
|
|
||||||
let messages = ctx.messages.read().unwrap().clone();
|
|
||||||
|
|
||||||
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
|
||||||
|
|
||||||
let mut messages = if offset > 0 {
|
|
||||||
let mut buf = vec![0; offset];
|
|
||||||
buf.append(&mut messages.clone());
|
|
||||||
buf
|
|
||||||
} else {
|
|
||||||
messages
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(splash) = &ctx.args.splash {
|
|
||||||
stream.write_all(
|
|
||||||
(splash.len() + messages.len() + offset)
|
|
||||||
.to_string()
|
|
||||||
.as_bytes(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut id = vec![0];
|
|
||||||
stream.read_exact(&mut id)?;
|
|
||||||
|
|
||||||
if id[0] == 0x01 {
|
|
||||||
messages.append(&mut splash.clone().as_bytes().to_vec());
|
|
||||||
stream.write_all(&messages)?;
|
|
||||||
} else if id[0] == 0x02 {
|
|
||||||
let mut buf = vec![0; 10];
|
|
||||||
let size = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(size);
|
|
||||||
|
|
||||||
let len: usize = String::from_utf8(buf)?.parse()?;
|
|
||||||
stream.write_all(&messages[(len - splash.len())..])?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stream.write_all((messages.len() + offset).to_string().as_bytes())?;
|
|
||||||
|
|
||||||
let mut id = vec![0];
|
|
||||||
stream.read_exact(&mut id)?;
|
|
||||||
|
|
||||||
if id[0] == 0x01 {
|
|
||||||
stream.write_all(&messages)?;
|
|
||||||
} else if id[0] == 0x02 {
|
|
||||||
let mut buf = vec![0; 10];
|
|
||||||
let size = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(size);
|
|
||||||
|
|
||||||
let len: usize = String::from_utf8(buf)?.parse()?;
|
|
||||||
stream.write_all(&messages[len..])?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if buf[0] == 0x01 {
|
|
||||||
if !ctx.args.auth_only {
|
|
||||||
let mut buf = vec![0; 1024];
|
|
||||||
let size = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(size);
|
|
||||||
|
|
||||||
add_message(&mut buf, ctx.clone(), Some(addr.ip()), ctx.args.sanitize)?;
|
|
||||||
}
|
|
||||||
} else if buf[0] == 0x02 {
|
|
||||||
let mut buf = vec![0; 8192];
|
|
||||||
let size = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(size);
|
|
||||||
|
|
||||||
let msg = String::from_utf8_lossy(&buf).to_string();
|
|
||||||
|
|
||||||
let mut segments = msg.split("\n");
|
|
||||||
|
|
||||||
let Some(name) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(password) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(text) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(acc) = ctx.get_account(name) {
|
|
||||||
if acc.check_password(password) {
|
|
||||||
add_message(
|
|
||||||
&mut text.as_bytes().to_vec(),
|
|
||||||
ctx.clone(),
|
|
||||||
None,
|
|
||||||
ctx.args.sanitize,
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
stream.write_all(&[0x02])?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stream.write_all(&[0x01])?;
|
|
||||||
}
|
|
||||||
} else if buf[0] == 0x03 {
|
|
||||||
let mut buf = vec![0; 1024];
|
|
||||||
let size = stream.read(&mut buf)?;
|
|
||||||
buf.truncate(size);
|
|
||||||
|
|
||||||
let msg = String::from_utf8_lossy(&buf).to_string();
|
|
||||||
|
|
||||||
let mut segments = msg.split("\n");
|
|
||||||
|
|
||||||
let Some(name) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let Some(password) = segments.next() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let addr = addr.ip().to_string();
|
|
||||||
|
|
||||||
let now: i64 = Local::now().timestamp_millis();
|
|
||||||
|
|
||||||
if ctx.get_account(name).is_some()
|
|
||||||
|| (if let Some(acc) = ctx.get_account_by_addr(&addr) {
|
|
||||||
((now - acc.date()) as usize) < 1000 * ctx.args.register_timeout
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
})
|
|
||||||
{
|
|
||||||
stream.write_all(&[0x01])?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let account = Account::new(name.to_string(), password.to_string(), addr, now);
|
|
||||||
|
|
||||||
println!("user registered: {name}");
|
|
||||||
|
|
||||||
ctx.push_account(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept_stream(
|
fn accept_stream(
|
||||||
stream: impl Read + Write,
|
stream: impl Read + Write,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@ -622,7 +85,7 @@ fn run_normal_listener(ctx: Arc<Context>) {
|
|||||||
match accept_stream(stream, addr, ctx) {
|
match accept_stream(stream, addr, ctx) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{}", e)
|
debug!("{}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -676,7 +139,7 @@ fn run_secure_listener(ctx: Arc<Context>) {
|
|||||||
match accept_stream(stream, addr, ctx) {
|
match accept_stream(stream, addr, ctx) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{}", e)
|
debug!("{}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -744,6 +207,8 @@ struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
colog::init();
|
||||||
|
|
||||||
let args = Arc::new(Args::parse());
|
let args = Arc::new(Args::parse());
|
||||||
|
|
||||||
let context = Arc::new(Context::new(
|
let context = Arc::new(Context::new(
|
||||||
@ -752,7 +217,7 @@ fn main() {
|
|||||||
args.accounts_file.clone(),
|
args.accounts_file.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
println!("Server started on {}", &args.host);
|
info!("Server started on {}", &args.host);
|
||||||
|
|
||||||
if args.enable_ssl {
|
if args.enable_ssl {
|
||||||
run_secure_listener(context);
|
run_secure_listener(context);
|
||||||
|
92
src/rac.rs
Normal file
92
src/rac.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::{Read, Write},
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{ctx::Context, logic::*};
|
||||||
|
|
||||||
|
pub fn accept_rac_stream(
|
||||||
|
mut stream: impl Read + Write,
|
||||||
|
addr: SocketAddr,
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut buf = vec![0];
|
||||||
|
stream.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
if buf[0] == 0x00 {
|
||||||
|
let total_size = on_total_size(ctx.clone(), addr.clone())?;
|
||||||
|
stream.write_all(total_size.to_string().as_bytes())?;
|
||||||
|
|
||||||
|
let mut id = vec![0];
|
||||||
|
stream.read_exact(&mut id)?;
|
||||||
|
|
||||||
|
if id[0] == 0x01 {
|
||||||
|
stream.write_all(&on_total_data(ctx.clone(), addr.clone(), Some(total_size))?)?;
|
||||||
|
} else if id[0] == 0x02 {
|
||||||
|
let mut buf = vec![0; 10];
|
||||||
|
let size = stream.read(&mut buf)?;
|
||||||
|
buf.truncate(size);
|
||||||
|
|
||||||
|
let client_has: u64 = String::from_utf8(buf)?.parse()?;
|
||||||
|
stream.write_all(&on_chunked_data(
|
||||||
|
ctx.clone(),
|
||||||
|
addr.clone(),
|
||||||
|
Some(total_size),
|
||||||
|
client_has,
|
||||||
|
)?)?;
|
||||||
|
}
|
||||||
|
} else if buf[0] == 0x01 {
|
||||||
|
let mut buf = vec![0; 1024];
|
||||||
|
let size = stream.read(&mut buf)?;
|
||||||
|
buf.truncate(size);
|
||||||
|
|
||||||
|
on_send_message(ctx.clone(), addr.clone(), buf)?;
|
||||||
|
} else if buf[0] == 0x02 {
|
||||||
|
let mut buf = vec![0; 8192];
|
||||||
|
let size = stream.read(&mut buf)?;
|
||||||
|
buf.truncate(size);
|
||||||
|
|
||||||
|
let msg = String::from_utf8_lossy(&buf).to_string();
|
||||||
|
|
||||||
|
let mut segments = msg.split("\n");
|
||||||
|
|
||||||
|
let Some(name) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(password) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(text) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(resp_id) =
|
||||||
|
on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)?
|
||||||
|
{
|
||||||
|
stream.write_all(&[resp_id])?;
|
||||||
|
}
|
||||||
|
} else if buf[0] == 0x03 {
|
||||||
|
let mut buf = vec![0; 1024];
|
||||||
|
let size = stream.read(&mut buf)?;
|
||||||
|
buf.truncate(size);
|
||||||
|
|
||||||
|
let msg = String::from_utf8_lossy(&buf).to_string();
|
||||||
|
|
||||||
|
let mut segments = msg.split("\n");
|
||||||
|
|
||||||
|
let Some(name) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(password) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)? {
|
||||||
|
stream.write_all(&[resp_id])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
113
src/wrac.rs
Normal file
113
src/wrac.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::{Read, Write},
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tungstenite::{Bytes, Message, accept};
|
||||||
|
|
||||||
|
use crate::{ctx::Context, logic::*};
|
||||||
|
|
||||||
|
pub fn accept_wrac_stream(
|
||||||
|
stream: impl Read + Write,
|
||||||
|
addr: SocketAddr,
|
||||||
|
ctx: Arc<Context>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut websocket = match accept(stream) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => return Err(format!("accept websocket error: {}", e).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sent_size = None;
|
||||||
|
|
||||||
|
while let Ok(msg) = websocket.read() {
|
||||||
|
if let Some(data) = match msg {
|
||||||
|
Message::Binary(o) => Some(o.to_vec()),
|
||||||
|
Message::Text(o) => Some(o.as_bytes().to_vec()),
|
||||||
|
Message::Close(_) => return Ok(()),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
let mut data = data;
|
||||||
|
let Some(id) = data.drain(..1).next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if id == 0x00 {
|
||||||
|
if data.is_empty() {
|
||||||
|
let total_size = on_total_size(ctx.clone(), addr.clone())?;
|
||||||
|
sent_size = Some(total_size);
|
||||||
|
|
||||||
|
websocket.write(Message::Binary(Bytes::from(
|
||||||
|
total_size.to_string().as_bytes().to_vec(),
|
||||||
|
)))?;
|
||||||
|
websocket.flush()?;
|
||||||
|
} else {
|
||||||
|
let Some(id) = data.drain(..1).next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if id == 0x01 {
|
||||||
|
websocket.write(Message::Binary(Bytes::from(on_total_data(
|
||||||
|
ctx.clone(),
|
||||||
|
addr.clone(),
|
||||||
|
sent_size,
|
||||||
|
)?)))?;
|
||||||
|
websocket.flush()?;
|
||||||
|
} else if id == 0x02 {
|
||||||
|
let client_has = String::from_utf8(data)?.parse()?;
|
||||||
|
websocket.write(Message::Binary(Bytes::from(on_chunked_data(
|
||||||
|
ctx.clone(),
|
||||||
|
addr.clone(),
|
||||||
|
sent_size,
|
||||||
|
client_has,
|
||||||
|
)?)))?;
|
||||||
|
websocket.flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if id == 0x01 {
|
||||||
|
on_send_message(ctx.clone(), addr.clone(), data)?;
|
||||||
|
} else if id == 0x02 {
|
||||||
|
let msg = String::from_utf8_lossy(&data).to_string();
|
||||||
|
|
||||||
|
let mut segments = msg.split("\n");
|
||||||
|
|
||||||
|
let Some(name) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(password) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(text) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(resp_id) =
|
||||||
|
on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)?
|
||||||
|
{
|
||||||
|
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
|
||||||
|
websocket.flush()?;
|
||||||
|
}
|
||||||
|
} else if id == 0x03 {
|
||||||
|
let msg = String::from_utf8_lossy(&data).to_string();
|
||||||
|
|
||||||
|
let mut segments = msg.split("\n");
|
||||||
|
|
||||||
|
let Some(name) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(password) = segments.next() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)?
|
||||||
|
{
|
||||||
|
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
|
||||||
|
websocket.flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user