mirror of
https://github.com/MeexReay/sRAC.git
synced 2025-06-24 10:32:58 +03:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
db20437749 | |||
edd9085304 | |||
3c6f9fb006 | |||
a5120ddb82 | |||
83d1726969 | |||
f4ad746453 | |||
a5ea227748 | |||
d3bb035fda | |||
![]() |
ae9fb60eab | ||
245c677c4c | |||
![]() |
87c5a7ce42 | ||
f262536e39 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/result
|
/result
|
||||||
/messages.txt
|
/messages.txt
|
||||||
/accounts.txt
|
/accounts.txt
|
||||||
|
/.idea
|
645
Cargo.lock
generated
645
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -5,11 +5,18 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.40"
|
chrono = "0.4.40"
|
||||||
bRAC = { git = "https://github.com/MeexReay/bRAC.git", default-features = false, tag = "0.1.2+2.0" }
|
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
rand = "0.9.0"
|
rand = "0.9.1"
|
||||||
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"
|
||||||
colog = "1.3.0"
|
colog = "1.3.0"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
|
regex = "1.11.1"
|
||||||
|
colored = "3.0.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
|
||||||
|
[dependencies.bRAC]
|
||||||
|
git = "https://github.com/MeexReay/bRAC"
|
||||||
|
rev = "f3b6cbd01c5443e03d012c3c866487ba19785baf"
|
||||||
|
default-features = false
|
43
README.md
43
README.md
@ -1,29 +1,32 @@
|
|||||||
# sRAC
|
# sRAC
|
||||||
simple server for RAC
|
simple server for RAC
|
||||||
|
|
||||||
## Usage
|
## features
|
||||||
|
|
||||||
|
- 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 clone https://github.com/MeexReay/sRAC.git; cd sRAC
|
||||||
cd sRAC
|
cargo run -- -H rac://127.0.0.1:42666
|
||||||
cargo run -- -H 127.0.0.1:42666
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### My server config
|
## roadmap
|
||||||
|
|
||||||
```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
|
|
||||||
|
|
||||||
- [ ] Notifications by ip
|
|
||||||
- [ ] Server commands
|
|
||||||
- [x] WRAC protocol
|
- [x] WRAC protocol
|
||||||
- [x] RACS protocol
|
- [x] RACS protocol
|
||||||
|
- [x] Proxy-mode
|
||||||
|
- [ ] Notifications by ip (private messages)
|
||||||
|
- [ ] Server commands
|
||||||
|
|
||||||
|
## license
|
||||||
|
|
||||||
|
This project is licensed under the WTFPL. Do what the fuck you want to.
|
4
shell.nix
Normal file
4
shell.nix
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [ pkg-config openssl ];
|
||||||
|
}
|
76
src/ctx.rs
76
src/ctx.rs
@ -11,13 +11,15 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bRAC::{chat::format_message, util::sanitize_text};
|
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
use chrono::{DateTime, Local, TimeZone};
|
||||||
use log::info;
|
use log::info;
|
||||||
use md5::{Digest, Md5};
|
use md5::{Digest, Md5};
|
||||||
use rand::{Rng, distr::Alphanumeric};
|
use rand::{Rng, distr::Alphanumeric};
|
||||||
|
|
||||||
use crate::Args;
|
use crate::{
|
||||||
|
Args,
|
||||||
|
util::{format_message, read_string, read_u32, sanitize_text},
|
||||||
|
};
|
||||||
|
|
||||||
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() {
|
||||||
@ -69,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.clone())),
|
messages: RwLock::new(load_messages(messages_file)),
|
||||||
accounts: RwLock::new(load_accounts(accounts_file.clone())),
|
accounts: RwLock::new(load_accounts(accounts_file)),
|
||||||
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()),
|
||||||
@ -80,7 +82,6 @@ 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)?;
|
||||||
@ -110,27 +111,26 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
|
pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
|
||||||
for acc in self.accounts.read().unwrap().iter().rev() {
|
self.accounts
|
||||||
if acc.addr() == addr {
|
.read()
|
||||||
return Some(acc.clone());
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
}
|
.find(|acc| acc.addr() == addr)
|
||||||
None
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account(&self, name: &str) -> Option<Account> {
|
pub fn get_account(&self, name: &str) -> Option<Account> {
|
||||||
for acc in self.accounts.read().unwrap().iter() {
|
self.accounts
|
||||||
if acc.name() == name {
|
.read()
|
||||||
return Some(acc.clone());
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
}
|
.find(|acc| acc.name() == name)
|
||||||
None
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
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,41 +213,21 @@ 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 text = Cursor::new(text);
|
let mut cursor = Cursor::new(text);
|
||||||
|
|
||||||
let mut name_len = [0; 4];
|
let name_len = read_u32(&mut cursor)? as usize;
|
||||||
text.read_exact(&mut name_len)?;
|
let salt_len = read_u32(&mut cursor)? as usize;
|
||||||
let name_len = u32::from_le_bytes(name_len) as usize;
|
let addr_len = read_u32(&mut cursor)? as usize;
|
||||||
|
let pass_len = read_u32(&mut cursor)? as usize;
|
||||||
let mut salt_len = [0; 4];
|
let name = read_string(&mut cursor, name_len)?;
|
||||||
text.read_exact(&mut salt_len)?;
|
let salt = read_string(&mut cursor, salt_len)?;
|
||||||
let salt_len = u32::from_le_bytes(salt_len) as usize;
|
let addr = read_string(&mut cursor, addr_len)?;
|
||||||
|
|
||||||
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];
|
||||||
text.read_exact(&mut pass)?;
|
cursor.read_exact(&mut pass)?;
|
||||||
|
|
||||||
let mut date = [0; 8];
|
let mut date = [0; 8];
|
||||||
text.read_exact(&mut date)?;
|
cursor.read_exact(&mut date)?;
|
||||||
let date = i64::from_le_bytes(date);
|
let date = i64::from_le_bytes(date);
|
||||||
|
|
||||||
Ok(Account {
|
Ok(Account {
|
||||||
|
83
src/logic.rs
83
src/logic.rs
@ -4,12 +4,24 @@ use std::{
|
|||||||
sync::{Arc, atomic::Ordering},
|
sync::{Arc, atomic::Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bRAC::proto::{connect, read_messages, register_user, send_message, send_message_auth};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::ctx::{Account, Context, add_message};
|
use crate::ctx::{Account, Context, add_message};
|
||||||
|
|
||||||
pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Error>> {
|
pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return read_messages(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
1024, // TODO: softcode this
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
)?
|
||||||
|
.map(|o| o.1 as u64)
|
||||||
|
.ok_or("err on reading in proxy mode".into()); // TODO: fix reading two times
|
||||||
|
}
|
||||||
|
|
||||||
let messages_len = ctx.messages.read().unwrap().len() as u64;
|
let messages_len = ctx.messages.read().unwrap().len() as u64;
|
||||||
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
|
|
||||||
@ -23,8 +35,19 @@ pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Er
|
|||||||
pub fn on_total_data(
|
pub fn on_total_data(
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
_: SocketAddr,
|
_: SocketAddr,
|
||||||
_: Option<u64>, // sent_size
|
_sent_size: Option<u64>,
|
||||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return read_messages(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
1024, // TODO: softcode this
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
)?
|
||||||
|
.map(|o| (o.0.join("\n") + "\n").as_bytes().to_vec())
|
||||||
|
.ok_or("err on reading in proxy mode".into()); // TODO: fix reading two times
|
||||||
|
}
|
||||||
|
|
||||||
let mut messages = ctx.messages.read().unwrap().clone();
|
let mut messages = ctx.messages.read().unwrap().clone();
|
||||||
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
|
|
||||||
@ -46,9 +69,20 @@ pub fn on_total_data(
|
|||||||
pub fn on_chunked_data(
|
pub fn on_chunked_data(
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
_: SocketAddr,
|
_: SocketAddr,
|
||||||
_: Option<u64>, // sent_size
|
_sent_size: Option<u64>,
|
||||||
client_has: u64,
|
client_has: u64,
|
||||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return read_messages(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
1024, // TODO: softcode this
|
||||||
|
client_has as usize,
|
||||||
|
true,
|
||||||
|
)?
|
||||||
|
.map(|o| (o.0.join("\n") + "\n").as_bytes().to_vec())
|
||||||
|
.ok_or("err on reading in proxy mode".into());
|
||||||
|
}
|
||||||
|
|
||||||
let messages = ctx.messages.read().unwrap().clone();
|
let messages = ctx.messages.read().unwrap().clone();
|
||||||
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
let offset = ctx.messages_offset.load(Ordering::SeqCst);
|
||||||
let client_has = if let Some(splash) = &ctx.args.splash {
|
let client_has = if let Some(splash) = &ctx.args.splash {
|
||||||
@ -70,11 +104,18 @@ pub fn on_send_message(
|
|||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
message: Vec<u8>,
|
message: Vec<u8>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return send_message(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
&String::from_utf8_lossy(&message),
|
||||||
|
); // TODO: make brac accept message in bytes
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.args.auth_only {
|
if !ctx.args.auth_only {
|
||||||
let mut message = message;
|
let mut message = message;
|
||||||
message.truncate(ctx.args.message_limit);
|
message.truncate(ctx.args.message_limit);
|
||||||
|
|
||||||
add_message(&message.clone(), ctx.clone(), Some(addr.ip()))?;
|
add_message(&message, ctx, Some(addr.ip()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -86,18 +127,31 @@ pub fn on_send_auth_message(
|
|||||||
password: &str,
|
password: &str,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Result<Option<u8>, Box<dyn Error>> {
|
) -> Result<Option<u8>, Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return match send_message_auth(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
text,
|
||||||
|
) {
|
||||||
|
Ok(0) => Ok(None),
|
||||||
|
Ok(n) => Ok(Some(n)),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(acc) = ctx.get_account(name) {
|
if let Some(acc) = ctx.get_account(name) {
|
||||||
if acc.check_password(password) {
|
if acc.check_password(password) {
|
||||||
let mut name = name.to_string();
|
let mut name = name.to_string();
|
||||||
name.truncate(256); // FIXME: softcode this
|
name.truncate(256); // TODO: softcode this
|
||||||
|
|
||||||
let mut password = password.to_string();
|
let mut password = password.to_string();
|
||||||
password.truncate(256); // FIXME: softcode this
|
password.truncate(256); // TODO: softcode this
|
||||||
|
|
||||||
let mut text = text.to_string();
|
let mut text = text.to_string();
|
||||||
text.truncate(ctx.args.message_limit);
|
text.truncate(ctx.args.message_limit);
|
||||||
|
|
||||||
add_message(&text.as_bytes(), ctx.clone(), None)?;
|
add_message(format!("<{name}> {text}").as_bytes(), ctx, None)?;
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
@ -114,6 +168,19 @@ pub fn on_register_user(
|
|||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<Option<u8>, Box<dyn Error>> {
|
) -> Result<Option<u8>, Box<dyn Error>> {
|
||||||
|
if let Some(url) = ctx.args.proxy_to.as_ref() {
|
||||||
|
return Ok(
|
||||||
|
match register_user(
|
||||||
|
&mut connect(url, ctx.args.use_proxy.clone())?,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
) {
|
||||||
|
Ok(true) => None,
|
||||||
|
_ => Some(0x01),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let addr = addr.ip().to_string();
|
let addr = addr.ip().to_string();
|
||||||
|
|
||||||
let now: i64 = Local::now().timestamp_millis();
|
let now: i64 = Local::now().timestamp_millis();
|
||||||
@ -136,3 +203,7 @@ pub fn on_register_user(
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_server_info(_: Arc<Context>, _: SocketAddr) -> Result<(u8, String), Box<dyn Error>> {
|
||||||
|
Ok((0x03, format!("sRAC {}", env!("CARGO_PKG_VERSION"))))
|
||||||
|
}
|
||||||
|
22
src/main.rs
22
src/main.rs
@ -1,5 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bRAC::proto::parse_rac_url;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
@ -8,11 +9,12 @@ use crate::{ctx::Context, proto::run_listener};
|
|||||||
pub mod ctx;
|
pub mod ctx;
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version)]
|
#[command(version)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Server host
|
/// Server host (RAC URL)
|
||||||
#[arg(short = 'H', long)]
|
#[arg(short = 'H', long)]
|
||||||
host: String,
|
host: String,
|
||||||
|
|
||||||
@ -52,10 +54,6 @@ pub struct Args {
|
|||||||
#[arg(long, default_value_t = 4194304)]
|
#[arg(long, default_value_t = 4194304)]
|
||||||
messages_total_limit: usize,
|
messages_total_limit: usize,
|
||||||
|
|
||||||
/// Enable SSL (RACS)
|
|
||||||
#[arg(short = 'l', long)]
|
|
||||||
enable_ssl: bool,
|
|
||||||
|
|
||||||
/// Set ssl certificate path (x509)
|
/// Set ssl certificate path (x509)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
ssl_key: Option<String>,
|
ssl_key: Option<String>,
|
||||||
@ -64,9 +62,13 @@ pub struct Args {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
ssl_cert: Option<String>,
|
ssl_cert: Option<String>,
|
||||||
|
|
||||||
/// Enable WRAC
|
/// Enable Proxy-Mode (RAC URL)
|
||||||
#[arg(short = 'w', long)]
|
#[arg(short = 'P', long)]
|
||||||
enable_wrac: bool,
|
proxy_to: Option<String>,
|
||||||
|
|
||||||
|
/// Use Socks5 proxy (to connect to the server in proxy-mode)
|
||||||
|
#[arg(long)]
|
||||||
|
use_proxy: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -82,5 +84,7 @@ fn main() {
|
|||||||
|
|
||||||
info!("Server started on {}", &args.host);
|
info!("Server started on {}", &args.host);
|
||||||
|
|
||||||
run_listener(context);
|
let (host, ssl, wrac) = parse_rac_url(&args.host).expect("INVALID RAC URL");
|
||||||
|
|
||||||
|
run_listener(context, &host, ssl, wrac);
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,9 @@ fn accept_stream(
|
|||||||
stream: impl Read + Write,
|
stream: impl Read + Write,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
|
wrac: bool,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
if ctx.args.enable_wrac {
|
if wrac {
|
||||||
accept_wrac_stream(stream, addr, ctx)?;
|
accept_wrac_stream(stream, addr, ctx)?;
|
||||||
} else {
|
} else {
|
||||||
accept_rac_stream(stream, addr, ctx)?;
|
accept_rac_stream(stream, addr, ctx)?;
|
||||||
@ -34,9 +35,8 @@ fn accept_stream(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_normal_listener(ctx: Arc<Context>) {
|
fn run_normal_listener(ctx: Arc<Context>, host: &str, wrac: bool) {
|
||||||
let listener =
|
let listener = TcpListener::bind(host).expect("error trying bind to the provided addr");
|
||||||
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
|
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
let Ok(stream) = stream else { continue };
|
let Ok(stream) = stream else { continue };
|
||||||
@ -47,7 +47,7 @@ fn run_normal_listener(ctx: Arc<Context>) {
|
|||||||
let Ok(addr) = stream.peer_addr() else {
|
let Ok(addr) = stream.peer_addr() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match accept_stream(stream, addr, ctx) {
|
match accept_stream(stream, addr, ctx, wrac) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("{}", e)
|
debug!("{}", e)
|
||||||
@ -57,9 +57,8 @@ fn run_normal_listener(ctx: Arc<Context>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_secure_listener(ctx: Arc<Context>) {
|
fn run_secure_listener(ctx: Arc<Context>, host: &str, wrac: bool) {
|
||||||
let listener =
|
let listener = TcpListener::bind(host).expect("error trying bind to the provided addr");
|
||||||
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
|
|
||||||
|
|
||||||
let server_config = Arc::new(
|
let server_config = Arc::new(
|
||||||
ServerConfig::builder()
|
ServerConfig::builder()
|
||||||
@ -101,7 +100,7 @@ fn run_secure_listener(ctx: Arc<Context>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match accept_stream(stream, addr, ctx) {
|
match accept_stream(stream, addr, ctx, wrac) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("{}", e)
|
debug!("{}", e)
|
||||||
@ -111,10 +110,10 @@ fn run_secure_listener(ctx: Arc<Context>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_listener(ctx: Arc<Context>) {
|
pub fn run_listener(ctx: Arc<Context>, host: &str, ssl: bool, wrac: bool) {
|
||||||
if ctx.args.enable_ssl {
|
if ssl {
|
||||||
run_secure_listener(ctx);
|
run_secure_listener(ctx, host, wrac);
|
||||||
} else {
|
} else {
|
||||||
run_normal_listener(ctx);
|
run_normal_listener(ctx, host, wrac);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.clone())?;
|
let total_size = on_total_size(ctx.clone(), addr)?;
|
||||||
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.clone(), Some(total_size))?)?;
|
stream.write_all(&on_total_data(ctx.clone(), addr, 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.clone(),
|
addr,
|
||||||
Some(total_size),
|
Some(total_size),
|
||||||
client_has,
|
client_has,
|
||||||
)?)?;
|
)?)?;
|
||||||
@ -42,9 +42,9 @@ 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.clone(), buf)?;
|
on_send_message(ctx.clone(), addr, 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]; // TODO: softcode this (512 = name + password)
|
||||||
let size = stream.read(&mut buf)?;
|
let size = stream.read(&mut buf)?;
|
||||||
buf.truncate(size);
|
buf.truncate(size);
|
||||||
|
|
||||||
@ -62,9 +62,7 @@ pub fn accept_rac_stream(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
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)?
|
|
||||||
{
|
|
||||||
stream.write_all(&[resp_id])?;
|
stream.write_all(&[resp_id])?;
|
||||||
}
|
}
|
||||||
} else if buf[0] == 0x03 {
|
} else if buf[0] == 0x03 {
|
||||||
@ -83,9 +81,17 @@ pub fn accept_rac_stream(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)? {
|
if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? {
|
||||||
stream.write_all(&[resp_id])?;
|
stream.write_all(&[resp_id])?;
|
||||||
}
|
}
|
||||||
|
} else if buf[0] == 0x69 {
|
||||||
|
let (protocol_version, name) = on_server_info(ctx.clone(), addr)?;
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.push(protocol_version);
|
||||||
|
data.append(&mut name.as_bytes().to_vec());
|
||||||
|
|
||||||
|
stream.write_all(&data)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -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.clone())?;
|
let total_size = on_total_size(ctx.clone(), addr)?;
|
||||||
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.clone(),
|
addr,
|
||||||
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.clone(),
|
addr,
|
||||||
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.clone(), data)?;
|
on_send_message(ctx.clone(), addr, 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.clone(), name, password, text)?
|
on_send_auth_message(ctx.clone(), addr, 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,15 +96,24 @@ 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.clone(), name, password)?
|
if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? {
|
||||||
{
|
|
||||||
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
|
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
|
||||||
websocket.flush()?;
|
websocket.flush()?;
|
||||||
}
|
}
|
||||||
|
} else if id == 0x69 {
|
||||||
|
let (protocol_version, name) = on_server_info(ctx.clone(), addr)?;
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.push(protocol_version);
|
||||||
|
data.append(&mut name.as_bytes().to_vec());
|
||||||
|
|
||||||
|
websocket.write(Message::Binary(Bytes::from(data)))?;
|
||||||
|
websocket.flush()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
116
src/util.rs
Normal file
116
src/util.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
use std::{error::Error, io::Read};
|
||||||
|
|
||||||
|
use colored::{Color, Colorize};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap();
|
||||||
|
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
||||||
|
pub static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![
|
||||||
|
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), // bRAC
|
||||||
|
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), // CRAB
|
||||||
|
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), Color::Magenta), // Mefidroniy
|
||||||
|
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan), // clRAC
|
||||||
|
];
|
||||||
|
pub static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
|
||||||
|
pub static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sanitize_text(input: &str) -> String {
|
||||||
|
let without_ansi = ANSI_REGEX.replace_all(input, "");
|
||||||
|
let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, "");
|
||||||
|
cleaned_text.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_message(enable_ip_viewing: bool, message: String) -> Option<String> {
|
||||||
|
let message = sanitize_text(&message);
|
||||||
|
|
||||||
|
let date = DATE_REGEX.captures(&message)?;
|
||||||
|
let (date, message) = (
|
||||||
|
date.get(1)?.as_str().to_string(),
|
||||||
|
date.get(2)?.as_str().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
|
||||||
|
(
|
||||||
|
Some(message.get(1)?.as_str().to_string()),
|
||||||
|
message.get(2)?.as_str().to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, message)
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = message
|
||||||
|
.trim_start_matches("(UNREGISTERED)")
|
||||||
|
.trim_start_matches("(UNAUTHORIZED)")
|
||||||
|
.trim_start_matches("(UNAUTHENTICATED)")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
+ " ";
|
||||||
|
|
||||||
|
let prefix = if enable_ip_viewing {
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
format!(
|
||||||
|
"{}{} [{}]",
|
||||||
|
ip,
|
||||||
|
" ".repeat(if 15 >= ip.chars().count() {
|
||||||
|
15 - ip.chars().count()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}),
|
||||||
|
date
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{} [{}]", " ".repeat(15), date)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("[{}]", date)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(if let Some(captures) = find_username_color(&message) {
|
||||||
|
let nick = captures.0;
|
||||||
|
let content = captures.1;
|
||||||
|
let color = captures.2;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} {} {}",
|
||||||
|
prefix.white().dimmed(),
|
||||||
|
format!("<{}>", nick).color(color).bold(),
|
||||||
|
content.white().blink()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{} {}", prefix.white().dimmed(), message.white().blink())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||||
|
for (re, color) in COLORED_USERNAMES.iter() {
|
||||||
|
if let Some(captures) = re.captures(message) {
|
||||||
|
return Some((
|
||||||
|
captures[1].to_string(),
|
||||||
|
captures[2].to_string(),
|
||||||
|
color.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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