Compare commits

..

No commits in common. "7843be190662e98c33819bd5694b39c7a62b1544" and "8dfb087e784ec617084b92243035f63f2481f181" have entirely different histories.

11 changed files with 692 additions and 887 deletions

View File

@ -1,6 +0,0 @@
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
}
}

91
Cargo.lock generated
View File

@ -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"
dependencies = [
"clap",
"colored 3.0.0",
"colored",
"lazy_static",
"rand",
"regex",
@ -275,33 +275,12 @@ dependencies = [
"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]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "colored"
version = "3.0.0"
@ -364,29 +343,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "equivalent"
version = "1.0.2"
@ -547,30 +503,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "jobserver"
version = "0.1.33"
@ -688,21 +620,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ppv-lite86"
version = "0.2.21"
@ -890,8 +807,6 @@ dependencies = [
"bRAC",
"chrono",
"clap",
"colog",
"log",
"md-5",
"rand",
"rustls",
@ -995,9 +910,9 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.27.0"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"data-encoding",

View File

@ -10,6 +10,4 @@ md-5 = "0.10.6"
rand = "0.9.0"
clap = { version = "4.5.36", features = ["derive"] }
rustls = "0.23.25"
tungstenite = "0.27.0"
colog = "1.3.0"
log = "0.4.27"
tungstenite = "0.26.2"

View File

@ -85,8 +85,6 @@ Server sends:
Default port - 42667
The same as RAC, but wrapped with TLS
# WRAC Protocol
Default port - 52666
@ -130,6 +128,4 @@ Server sends:
# WRACS Protocol
Default port - 52667
Just the same WRAC protocol but wrapped with TLS, like RACS
Default port - 52667

View File

@ -1,9 +0,0 @@
  ▗▆ ▇▄╴ ▊▗▍▍▖▏▄━╴ ▋╺▁▘▗▖▗▏▅▅▖▄▅▖▗▄▄╼▄▉┲▄▏
 ▗ ▂▃▃▂▂▃▂▁ ▝▏ ▌▌▖▌▌▎▊╵▂ ▗▂▇▊▌▎▎▊╻▃▉▋▂▋▍▎▎▎▉▗▗▍▍
▗▏ ▗ ▝▏ ▌ ▇▇▇▇▇▆▆╴▆ ╴▆▆▇▇▆▆▊▁▁▘▆▆▇▆╴▆▇▆╴▆▆╴
▋ ▊▏ ▗▆▏▗▆▎ ▌ ▊ 
▝▏ ▊▁ ▇▇▁▇▇▏▁▌ ▌ ▗▄▄▄ 
 ▝ ▇▅▘▇▇▇▝▅▆ ▗▏ ▝▃▃▋ ▊┓▁▋╺▁▆▉▌▉▌▗▂▆▉▖▘╸▖▗╴▉▖▁▁▁▗┎
  ▝▂ ▁▄╴ ▗▘▉▉ ▊▊▇▊▌┗▍▉▝▂▋▌▗▎▊▝▃╴▋▍ ▋▌▎▗▎▌▍
  ╴▆▄▃▂▁▁▃▃▅▇ ▇ ▇ ▇ ▇ ▇▇ ▇▇▝▇ ▇ ▇▇ ▆▇▇▇▇▇
[?25h

View File

@ -1,302 +0,0 @@
use std::{
collections::HashMap,
error::Error,
fs::{self, 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;
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
if let Some(accounts_file) = accounts_file.clone() {
if fs::exists(&accounts_file).expect("error checking accounts file") {
fs::read(&accounts_file)
.expect("error reading accounts file")
.split(|o| *o == b'\n')
.filter(|o| !o.is_empty())
.filter_map(|o| Account::from_bytes(o.to_vec()).ok())
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
}
}
fn load_messages(messages_file: Option<String>) -> Vec<u8> {
if let Some(messages_file) = messages_file.clone() {
if fs::exists(&messages_file).expect("error checking messages file") {
fs::read(&messages_file).expect("error reading messages file")
} else {
Vec::new()
}
} else {
Vec::new()
}
}
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>>>, // u32 - ip
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())),
messages_offset: AtomicU64::default(),
notifications: RwLock::new(HashMap::new()),
timeouts: RwLock::new(HashMap::new()),
}
}
pub fn push_message(&self, msg: Vec<u8>) -> Result<(), Box<dyn Error>> {
if let Some(messages_file) = self.messages_file.clone() {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(messages_file)?;
file.write_all(&msg)?;
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;
let content = content[offset..].to_vec();
*self.messages.write().unwrap() = content.clone();
self.messages_offset.store(offset as u64, Ordering::SeqCst);
if let Some(messages_file) = self.messages_file.clone() {
fs::write(messages_file, &content)?;
}
}
Ok(())
}
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) -> Result<(), Box<dyn Error>> {
if let Some(accounts_file) = self.accounts_file.clone() {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(accounts_file)?;
file.write_all(&acc.to_bytes())?;
file.write_all(b"\n")?;
file.flush()?;
}
self.accounts.write().unwrap().push(acc);
Ok(())
}
}
#[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>) -> Result<Self, Box<dyn Error>> {
let mut text = Cursor::new(text);
let mut name_len = [0; 4];
text.read_exact(&mut name_len)?;
let name_len = u32::from_le_bytes(name_len) as usize;
let mut salt_len = [0; 4];
text.read_exact(&mut salt_len)?;
let salt_len = u32::from_le_bytes(salt_len) as usize;
let mut addr_len = [0; 4];
text.read_exact(&mut addr_len)?;
let addr_len = u32::from_le_bytes(addr_len) as usize;
let mut pass_len = [0; 4];
text.read_exact(&mut pass_len)?;
let pass_len = u32::from_le_bytes(pass_len) as usize;
let mut name = vec![0; name_len];
text.read_exact(&mut name)?;
let name = String::from_utf8_lossy(&name).to_string();
let mut salt = vec![0; salt_len];
text.read_exact(&mut salt)?;
let salt = String::from_utf8_lossy(&salt).to_string();
let mut addr = vec![0; addr_len];
text.read_exact(&mut addr)?;
let addr = String::from_utf8_lossy(&addr).to_string();
let mut pass = vec![0; pass_len];
text.read_exact(&mut pass)?;
let mut date = [0; 8];
text.read_exact(&mut date)?;
let date = i64::from_le_bytes(date);
Ok(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(
text: &[u8],
ctx: Arc<Context>,
addr: Option<IpAddr>,
) -> Result<(), Box<dyn Error>> {
let prefix = message_prefix(Local::now().timestamp_millis(), addr.map(|o| o.to_string()));
let mut msg = prefix.as_bytes().to_vec();
if ctx.args.sanitize {
msg.append(
&mut sanitize_text(&String::from_utf8_lossy(text))
.as_bytes()
.to_vec(),
);
} else {
msg.append(&mut text.to_vec());
}
if let Some(msg) = format_message(addr.is_some(), String::from_utf8_lossy(&msg).to_string()) {
info!("{}", msg);
}
msg.push(b'\n');
ctx.push_message(msg)?;
Ok(())
}

View File

@ -1,138 +0,0 @@
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 {
let mut message = message;
message.truncate(ctx.args.message_limit);
add_message(&message.clone(), ctx.clone(), Some(addr.ip()))?;
}
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) {
let mut name = name.to_string();
name.truncate(256); // FIXME: softcode this
let mut password = password.to_string();
password.truncate(256); // FIXME: softcode this
let mut text = text.to_string();
text.truncate(ctx.args.message_limit);
add_message(&text.as_bytes(), ctx.clone(), None)?;
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)
}

View File

@ -1,17 +1,691 @@
use std::sync::Arc;
use std::{
collections::HashMap,
error::Error,
fs::{self, OpenOptions},
io::{Cursor, Read, Write},
net::{IpAddr, SocketAddr, TcpListener},
sync::{
Arc, RwLock,
atomic::{AtomicUsize, Ordering},
},
thread,
time::Duration,
};
use bRAC::{chat::format_message, util::sanitize_text};
use chrono::{DateTime, Local, TimeZone};
use md5::{Digest, Md5};
use rand::{Rng, distr::Alphanumeric};
use clap::Parser;
use log::info;
use rustls::{
ServerConfig, ServerConnection, StreamOwned,
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
};
use tungstenite::{Bytes, Message, accept};
use crate::{ctx::Context, proto::run_listener};
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
if let Some(accounts_file) = accounts_file.clone() {
if fs::exists(&accounts_file).expect("error checking accounts file") {
fs::read(&accounts_file)
.expect("error reading accounts file")
.split(|o| *o == b'\n')
.filter(|o| !o.is_empty())
.map(|o| Account::from_bytes(o.to_vec()))
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
}
}
pub mod ctx;
pub mod logic;
pub mod proto;
fn load_messages(messages_file: Option<String>) -> Vec<u8> {
if let Some(messages_file) = messages_file.clone() {
if fs::exists(&messages_file).expect("error checking messages file") {
fs::read(&messages_file).expect("error reading messages file")
} else {
Vec::new()
}
} else {
Vec::new()
}
}
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(
stream: impl Read + Write,
addr: SocketAddr,
ctx: Arc<Context>,
) -> Result<(), Box<dyn Error>> {
if ctx.args.enable_wrac {
accept_wrac_stream(stream, addr, ctx)?;
} else {
accept_rac_stream(stream, addr, ctx)?;
}
Ok(())
}
fn run_normal_listener(ctx: Arc<Context>) {
let listener =
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let ctx = ctx.clone();
thread::spawn(move || {
let Ok(addr) = stream.peer_addr() else {
return;
};
match accept_stream(stream, addr, ctx) {
Ok(_) => {}
Err(e) => {
println!("{}", e)
}
}
});
}
}
fn run_secure_listener(ctx: Arc<Context>) {
let listener =
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
let server_config = Arc::new(
ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
CertificateDer::pem_file_iter(
ctx.args.ssl_cert.clone().expect("--ssl-cert is required"),
)
.unwrap()
.map(|cert| cert.unwrap())
.collect(),
PrivateKeyDer::from_pem_file(
ctx.args.ssl_key.clone().expect("--ssl-key is required"),
)
.unwrap(),
)
.unwrap(),
);
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let ctx = ctx.clone();
let server_config = server_config.clone();
thread::spawn(move || {
let Ok(addr) = stream.peer_addr() else {
return;
};
let Ok(connection) = ServerConnection::new(server_config) else {
return;
};
let mut stream = StreamOwned::new(connection, stream);
while stream.conn.is_handshaking() {
let Ok(_) = stream.conn.complete_io(&mut stream.sock) else {
return;
};
}
match accept_stream(stream, addr, ctx) {
Ok(_) => {}
Err(e) => {
println!("{}", e)
}
}
});
}
}
#[derive(Parser, Debug)]
#[command(version)]
pub struct Args {
struct Args {
/// Server host
#[arg(short = 'H', long)]
host: String,
@ -70,8 +744,6 @@ pub struct Args {
}
fn main() {
colog::init();
let args = Arc::new(Args::parse());
let context = Arc::new(Context::new(
@ -80,7 +752,11 @@ fn main() {
args.accounts_file.clone(),
));
info!("Server started on {}", &args.host);
println!("Server started on {}", &args.host);
run_listener(context);
if args.enable_ssl {
run_secure_listener(context);
} else {
run_normal_listener(context);
}
}

View File

@ -1,120 +0,0 @@
use std::{
error::Error,
io::{Read, Write},
net::{SocketAddr, TcpListener},
sync::Arc,
thread,
};
use log::debug;
use rustls::{
ServerConfig, ServerConnection, StreamOwned,
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
};
use crate::{
ctx::Context,
proto::{rac::accept_rac_stream, wrac::accept_wrac_stream},
};
pub mod rac;
pub mod wrac;
fn accept_stream(
stream: impl Read + Write,
addr: SocketAddr,
ctx: Arc<Context>,
) -> Result<(), Box<dyn Error>> {
if ctx.args.enable_wrac {
accept_wrac_stream(stream, addr, ctx)?;
} else {
accept_rac_stream(stream, addr, ctx)?;
}
Ok(())
}
fn run_normal_listener(ctx: Arc<Context>) {
let listener =
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let ctx = ctx.clone();
thread::spawn(move || {
let Ok(addr) = stream.peer_addr() else {
return;
};
match accept_stream(stream, addr, ctx) {
Ok(_) => {}
Err(e) => {
debug!("{}", e)
}
}
});
}
}
fn run_secure_listener(ctx: Arc<Context>) {
let listener =
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
let server_config = Arc::new(
ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
CertificateDer::pem_file_iter(
ctx.args.ssl_cert.clone().expect("--ssl-cert is required"),
)
.unwrap()
.map(|cert| cert.unwrap())
.collect(),
PrivateKeyDer::from_pem_file(
ctx.args.ssl_key.clone().expect("--ssl-key is required"),
)
.unwrap(),
)
.unwrap(),
);
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let ctx = ctx.clone();
let server_config = server_config.clone();
thread::spawn(move || {
let Ok(addr) = stream.peer_addr() else {
return;
};
let Ok(connection) = ServerConnection::new(server_config) else {
return;
};
let mut stream = StreamOwned::new(connection, stream);
while stream.conn.is_handshaking() {
let Ok(_) = stream.conn.complete_io(&mut stream.sock) else {
return;
};
}
match accept_stream(stream, addr, ctx) {
Ok(_) => {}
Err(e) => {
debug!("{}", e)
}
}
});
}
}
pub fn run_listener(ctx: Arc<Context>) {
if ctx.args.enable_ssl {
run_secure_listener(ctx);
} else {
run_normal_listener(ctx);
}
}

View File

@ -1,92 +0,0 @@
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; ctx.args.message_limit];
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; ctx.args.message_limit + 2 + 512]; // FIXME: softcode this (512 = name + password)
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(())
}

View File

@ -1,113 +0,0 @@
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(())
}