mirror of
https://github.com/MeexReay/sRAC.git
synced 2025-06-25 11:02:57 +03:00
Compare commits
No commits in common. "d3bb035fda00a7f7fca4fbfdeb5bb14743c1df3e" and "f262536e396638d7019ff58054ff078a790204cf" have entirely different histories.
d3bb035fda
...
f262536e39
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@
|
|||||||
/result
|
/result
|
||||||
/messages.txt
|
/messages.txt
|
||||||
/accounts.txt
|
/accounts.txt
|
||||||
/.idea
|
|
976
Cargo.lock
generated
976
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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" }
|
|
37
README.md
37
README.md
@ -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.
|
|
72
src/ctx.rs
72
src/ctx.rs
@ -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 {
|
||||||
|
@ -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])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()?;
|
||||||
}
|
}
|
||||||
|
20
src/util.rs
20
src/util.rs
@ -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())
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user