Compare commits

..

No commits in common. "d3bb035fda00a7f7fca4fbfdeb5bb14743c1df3e" and "f262536e396638d7019ff58054ff078a790204cf" have entirely different histories.

8 changed files with 91 additions and 1050 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@
/result /result
/messages.txt /messages.txt
/accounts.txt /accounts.txt
/.idea

976
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
chrono = "0.4.40" chrono = "0.4.40"
md-5 = "0.10.6" md-5 = "0.10.6"
rand = "0.9.1" 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.27.0" tungstenite = "0.27.0"
@ -15,4 +15,3 @@ log = "0.4.27"
regex = "1.11.1" regex = "1.11.1"
colored = "3.0.0" colored = "3.0.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
bRAC = { git = "https://github.com/MeexReay/bRAC.git" }

View File

@ -1,32 +1,29 @@
# sRAC # sRAC
simple server for RAC simple server for RAC
## features ## Usage
- RACv2.0 and WRACv2.0 protocols
- SSL encryption (via rustls)
- messages limits by size
- splash message
- message sanitizing (removes all shit)
- auth-only mode and accounts
- messages saving into file
- register and message timeouts
## usage
```bash ```bash
git clone https://github.com/MeexReay/sRAC.git; cd sRAC git clone https://github.com/MeexReay/sRAC
cargo run -- -H rac://127.0.0.1:42666 cd sRAC
cargo run -- -H 127.0.0.1:42666
``` ```
## roadmap ### My server config
```bash
cargo run -- \
--host 127.0.0.1:42666 \
--splash "please register (/register and /login commands in bRAC)" \
--messages-file "messages.txt" \
--accounts-file "accounts.txt" \
--register-timeout 3600 \
--sanitize \
--auth-only
```
## Roadmap
- [ ] Proxy-mode
- [ ] Notifications by ip - [ ] Notifications by ip
- [ ] Server commands - [ ] Server commands
- [x] WRAC protocol - [x] WRAC protocol
- [x] RACS protocol - [x] RACS protocol
## license
This project is licensed under the WTFPL. Do what the fuck you want to.

View File

@ -18,7 +18,7 @@ use rand::{Rng, distr::Alphanumeric};
use crate::{ use crate::{
Args, Args,
util::{format_message, read_string, read_u32, sanitize_text}, util::{format_message, sanitize_text},
}; };
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> { fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
@ -71,8 +71,8 @@ impl Context {
args, args,
messages_file: messages_file.clone(), messages_file: messages_file.clone(),
accounts_file: accounts_file.clone(), accounts_file: accounts_file.clone(),
messages: RwLock::new(load_messages(messages_file)), messages: RwLock::new(load_messages(messages_file.clone())),
accounts: RwLock::new(load_accounts(accounts_file)), accounts: RwLock::new(load_accounts(accounts_file.clone())),
messages_offset: AtomicU64::default(), messages_offset: AtomicU64::default(),
notifications: RwLock::new(HashMap::new()), notifications: RwLock::new(HashMap::new()),
timeouts: RwLock::new(HashMap::new()), timeouts: RwLock::new(HashMap::new()),
@ -82,6 +82,7 @@ impl Context {
pub fn push_message(&self, msg: Vec<u8>) -> Result<(), Box<dyn Error>> { pub fn push_message(&self, msg: Vec<u8>) -> Result<(), Box<dyn Error>> {
if let Some(messages_file) = self.messages_file.clone() { if let Some(messages_file) = self.messages_file.clone() {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true)
.append(true) .append(true)
.create(true) .create(true)
.open(messages_file)?; .open(messages_file)?;
@ -111,26 +112,27 @@ impl Context {
} }
pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> { pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
self.accounts for acc in self.accounts.read().unwrap().iter().rev() {
.read() if acc.addr() == addr {
.unwrap() return Some(acc.clone());
.iter() }
.find(|acc| acc.addr() == addr) }
.cloned() None
} }
pub fn get_account(&self, name: &str) -> Option<Account> { pub fn get_account(&self, name: &str) -> Option<Account> {
self.accounts for acc in self.accounts.read().unwrap().iter() {
.read() if acc.name() == name {
.unwrap() return Some(acc.clone());
.iter() }
.find(|acc| acc.name() == name) }
.cloned() None
} }
pub fn push_account(&self, acc: Account) -> Result<(), Box<dyn Error>> { pub fn push_account(&self, acc: Account) -> Result<(), Box<dyn Error>> {
if let Some(accounts_file) = self.accounts_file.clone() { if let Some(accounts_file) = self.accounts_file.clone() {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true)
.append(true) .append(true)
.create(true) .create(true)
.open(accounts_file)?; .open(accounts_file)?;
@ -213,21 +215,41 @@ impl Account {
} }
pub fn from_bytes(text: Vec<u8>) -> Result<Self, Box<dyn Error>> { pub fn from_bytes(text: Vec<u8>) -> Result<Self, Box<dyn Error>> {
let mut cursor = Cursor::new(text); let mut text = Cursor::new(text);
let name_len = read_u32(&mut cursor)? as usize; let mut name_len = [0; 4];
let salt_len = read_u32(&mut cursor)? as usize; text.read_exact(&mut name_len)?;
let addr_len = read_u32(&mut cursor)? as usize; let name_len = u32::from_le_bytes(name_len) as usize;
let pass_len = read_u32(&mut cursor)? as usize;
let name = read_string(&mut cursor, name_len)?; let mut salt_len = [0; 4];
let salt = read_string(&mut cursor, salt_len)?; text.read_exact(&mut salt_len)?;
let addr = read_string(&mut cursor, addr_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]; let mut pass = vec![0; pass_len];
cursor.read_exact(&mut pass)?; text.read_exact(&mut pass)?;
let mut date = [0; 8]; let mut date = [0; 8];
cursor.read_exact(&mut date)?; text.read_exact(&mut date)?;
let date = i64::from_le_bytes(date); let date = i64::from_le_bytes(date);
Ok(Account { Ok(Account {

View File

@ -16,14 +16,14 @@ pub fn accept_rac_stream(
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
if buf[0] == 0x00 { if buf[0] == 0x00 {
let total_size = on_total_size(ctx.clone(), addr)?; let total_size = on_total_size(ctx.clone(), addr.clone())?;
stream.write_all(total_size.to_string().as_bytes())?; stream.write_all(total_size.to_string().as_bytes())?;
let mut id = vec![0]; let mut id = vec![0];
stream.read_exact(&mut id)?; stream.read_exact(&mut id)?;
if id[0] == 0x01 { if id[0] == 0x01 {
stream.write_all(&on_total_data(ctx.clone(), addr, Some(total_size))?)?; stream.write_all(&on_total_data(ctx.clone(), addr.clone(), Some(total_size))?)?;
} else if id[0] == 0x02 { } else if id[0] == 0x02 {
let mut buf = vec![0; 10]; let mut buf = vec![0; 10];
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
@ -32,7 +32,7 @@ pub fn accept_rac_stream(
let client_has: u64 = String::from_utf8(buf)?.parse()?; let client_has: u64 = String::from_utf8(buf)?.parse()?;
stream.write_all(&on_chunked_data( stream.write_all(&on_chunked_data(
ctx.clone(), ctx.clone(),
addr, addr.clone(),
Some(total_size), Some(total_size),
client_has, client_has,
)?)?; )?)?;
@ -42,7 +42,7 @@ pub fn accept_rac_stream(
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
buf.truncate(size); buf.truncate(size);
on_send_message(ctx.clone(), addr, buf)?; on_send_message(ctx.clone(), addr.clone(), buf)?;
} else if buf[0] == 0x02 { } else if buf[0] == 0x02 {
let mut buf = vec![0; ctx.args.message_limit + 2 + 512]; // FIXME: softcode this (512 = name + password) let mut buf = vec![0; ctx.args.message_limit + 2 + 512]; // FIXME: softcode this (512 = name + password)
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
@ -62,7 +62,9 @@ pub fn accept_rac_stream(
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = on_send_auth_message(ctx.clone(), addr, name, password, text)? { if let Some(resp_id) =
on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)?
{
stream.write_all(&[resp_id])?; stream.write_all(&[resp_id])?;
} }
} else if buf[0] == 0x03 { } else if buf[0] == 0x03 {
@ -81,7 +83,7 @@ pub fn accept_rac_stream(
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? { if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)? {
stream.write_all(&[resp_id])?; stream.write_all(&[resp_id])?;
} }
} }

View File

@ -35,7 +35,7 @@ pub fn accept_wrac_stream(
if id == 0x00 { if id == 0x00 {
if data.is_empty() { if data.is_empty() {
let total_size = on_total_size(ctx.clone(), addr)?; let total_size = on_total_size(ctx.clone(), addr.clone())?;
sent_size = Some(total_size); sent_size = Some(total_size);
websocket.write(Message::Binary(Bytes::from( websocket.write(Message::Binary(Bytes::from(
@ -50,7 +50,7 @@ pub fn accept_wrac_stream(
if id == 0x01 { if id == 0x01 {
websocket.write(Message::Binary(Bytes::from(on_total_data( websocket.write(Message::Binary(Bytes::from(on_total_data(
ctx.clone(), ctx.clone(),
addr, addr.clone(),
sent_size, sent_size,
)?)))?; )?)))?;
websocket.flush()?; websocket.flush()?;
@ -58,7 +58,7 @@ pub fn accept_wrac_stream(
let client_has = String::from_utf8(data)?.parse()?; let client_has = String::from_utf8(data)?.parse()?;
websocket.write(Message::Binary(Bytes::from(on_chunked_data( websocket.write(Message::Binary(Bytes::from(on_chunked_data(
ctx.clone(), ctx.clone(),
addr, addr.clone(),
sent_size, sent_size,
client_has, client_has,
)?)))?; )?)))?;
@ -66,7 +66,7 @@ pub fn accept_wrac_stream(
} }
} }
} else if id == 0x01 { } else if id == 0x01 {
on_send_message(ctx.clone(), addr, data)?; on_send_message(ctx.clone(), addr.clone(), data)?;
} else if id == 0x02 { } else if id == 0x02 {
let msg = String::from_utf8_lossy(&data).to_string(); let msg = String::from_utf8_lossy(&data).to_string();
@ -83,7 +83,7 @@ pub fn accept_wrac_stream(
}; };
if let Some(resp_id) = if let Some(resp_id) =
on_send_auth_message(ctx.clone(), addr, name, password, text)? on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)?
{ {
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?; websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
websocket.flush()?; websocket.flush()?;
@ -96,12 +96,12 @@ pub fn accept_wrac_stream(
let Some(name) = segments.next() else { let Some(name) = segments.next() else {
return Ok(()); return Ok(());
}; };
let Some(password) = segments.next() else { let Some(password) = segments.next() else {
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? { if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)?
{
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?; websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
websocket.flush()?; websocket.flush()?;
} }

View File

@ -1,5 +1,3 @@
use std::{error::Error, io::Read};
use colored::{Color, Colorize}; use colored::{Color, Colorize};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
@ -96,21 +94,3 @@ pub fn find_username_color(message: &str) -> Option<(String, String, Color)> {
} }
None None
} }
pub fn read_u32<R>(cursor: &mut R) -> Result<u32, Box<dyn Error>>
where
R: Read,
{
let mut buf = [0; 4];
cursor.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn read_string<R>(cursor: &mut R, len: usize) -> Result<String, Box<dyn Error>>
where
R: Read,
{
let mut buf = vec![0; len];
cursor.read_exact(&mut buf)?;
String::from_utf8(buf).map_err(|e| e.into())
}