Denis Drakhnia
1 year ago
6 changed files with 227 additions and 0 deletions
@ -0,0 +1,13 @@ |
|||||||
|
[package] |
||||||
|
name = "admin" |
||||||
|
version = "0.1.0" |
||||||
|
license = "GPL-3.0-only" |
||||||
|
authors = ["Denis Drakhnia <numas13@gmail.com>"] |
||||||
|
edition = "2021" |
||||||
|
rust-version = "1.56" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
thiserror = "1.0.49" |
||||||
|
getopts = "0.2.21" |
||||||
|
blake2b_simd = "<0.6" |
||||||
|
xash3d-protocol = { path = "../protocol", version = "0.1.0" } |
@ -0,0 +1,106 @@ |
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
||||||
|
|
||||||
|
use std::process; |
||||||
|
|
||||||
|
use getopts::Options; |
||||||
|
use xash3d_protocol::admin; |
||||||
|
|
||||||
|
const BIN_NAME: &str = env!("CARGO_BIN_NAME"); |
||||||
|
const PKG_NAME: &str = env!("CARGO_PKG_NAME"); |
||||||
|
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); |
||||||
|
|
||||||
|
const DEFAULT_HOST: &str = "mentality.rip"; |
||||||
|
const DEFAULT_PORT: u16 = 27010; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct Cli { |
||||||
|
pub address: String, |
||||||
|
pub command: String, |
||||||
|
pub hash_len: usize, |
||||||
|
pub hash_key: String, |
||||||
|
pub hash_personal: String, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for Cli { |
||||||
|
fn default() -> Cli { |
||||||
|
Cli { |
||||||
|
address: format!("{}:{}", DEFAULT_HOST, DEFAULT_PORT), |
||||||
|
command: String::new(), |
||||||
|
hash_len: admin::HASH_LEN, |
||||||
|
hash_key: admin::HASH_KEY.to_owned(), |
||||||
|
hash_personal: admin::HASH_PERSONAL.to_owned(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn print_usage(opts: Options) { |
||||||
|
let brief = format!("Usage: {} [options]", BIN_NAME); |
||||||
|
print!("{}", opts.usage(&brief)); |
||||||
|
} |
||||||
|
|
||||||
|
fn print_version() { |
||||||
|
println!("{} v{}", PKG_NAME, PKG_VERSION); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn parse() -> Cli { |
||||||
|
let mut cli = Cli::default(); |
||||||
|
|
||||||
|
let args: Vec<_> = std::env::args().collect(); |
||||||
|
let mut opts = Options::new(); |
||||||
|
opts.optflag("h", "help", "print usage help"); |
||||||
|
opts.optflag("v", "version", "print program version"); |
||||||
|
let help = format!("address to connect [default: {}]", cli.address); |
||||||
|
opts.optopt("h", "host", &help, "ADDR"); |
||||||
|
let help = format!("hash length [default: {}]", cli.hash_len); |
||||||
|
opts.optopt("l", "hash-length", &help, "N"); |
||||||
|
let help = format!("hash key [default: {}]", cli.hash_key); |
||||||
|
opts.optopt("k", "hash-key", &help, "KEY"); |
||||||
|
let help = format!("hash personalization [default: {}]", cli.hash_personal); |
||||||
|
opts.optopt("p", "hash-personal", &help, "PERSONAL"); |
||||||
|
|
||||||
|
let matches = match opts.parse(&args[1..]) { |
||||||
|
Ok(m) => m, |
||||||
|
Err(e) => { |
||||||
|
eprintln!("{}", e); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if matches.opt_present("help") { |
||||||
|
print_usage(opts); |
||||||
|
process::exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
if matches.opt_present("version") { |
||||||
|
print_version(); |
||||||
|
process::exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(mut s) = matches.opt_str("host") { |
||||||
|
if !s.contains(':') { |
||||||
|
s.push_str(":27010"); |
||||||
|
} |
||||||
|
cli.address = s; |
||||||
|
} |
||||||
|
|
||||||
|
match matches.opt_get("hash-length") { |
||||||
|
Ok(Some(n)) => cli.hash_len = n, |
||||||
|
Ok(None) => {} |
||||||
|
Err(_) => { |
||||||
|
eprintln!("Invalid key length"); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(s) = matches.opt_str("hash-key") { |
||||||
|
cli.hash_key = s; |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(s) = matches.opt_str("hash-personal") { |
||||||
|
cli.hash_personal = s; |
||||||
|
} |
||||||
|
|
||||||
|
cli.command = matches.free.join(" "); |
||||||
|
cli |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
||||||
|
|
||||||
|
mod cli; |
||||||
|
|
||||||
|
use std::io; |
||||||
|
use std::net::UdpSocket; |
||||||
|
|
||||||
|
use blake2b_simd::Params; |
||||||
|
use thiserror::Error; |
||||||
|
use xash3d_protocol::{admin, master}; |
||||||
|
|
||||||
|
#[derive(Error, Debug)] |
||||||
|
enum Error { |
||||||
|
#[error("Unexpected response from master server")] |
||||||
|
UnexpectedPacket, |
||||||
|
#[error(transparent)] |
||||||
|
Protocol(#[from] xash3d_protocol::Error), |
||||||
|
#[error(transparent)] |
||||||
|
Io(#[from] io::Error), |
||||||
|
} |
||||||
|
|
||||||
|
fn send_command(cli: &cli::Cli) -> Result<(), Error> { |
||||||
|
let sock = UdpSocket::bind("0.0.0.0:0")?; |
||||||
|
sock.connect(&cli.address)?; |
||||||
|
|
||||||
|
let mut buf = [0; 512]; |
||||||
|
let n = admin::AdminChallenge.encode(&mut buf)?; |
||||||
|
sock.send(&buf[..n])?; |
||||||
|
|
||||||
|
let n = sock.recv(&mut buf)?; |
||||||
|
let challenge = match master::Packet::decode(&buf[..n])? { |
||||||
|
master::Packet::AdminChallengeResponse(p) => p.challenge, |
||||||
|
_ => return Err(Error::UnexpectedPacket), |
||||||
|
}; |
||||||
|
|
||||||
|
println!("Password:"); |
||||||
|
let mut password = String::new(); |
||||||
|
io::stdin().read_line(&mut password)?; |
||||||
|
if password.ends_with('\n') { |
||||||
|
password.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
let hash = Params::new() |
||||||
|
.hash_length(cli.hash_len) |
||||||
|
.key(cli.hash_key.as_bytes()) |
||||||
|
.personal(cli.hash_personal.as_bytes()) |
||||||
|
.to_state() |
||||||
|
.update(password.as_bytes()) |
||||||
|
.update(&challenge.to_le_bytes()) |
||||||
|
.finalize(); |
||||||
|
|
||||||
|
let n = admin::AdminCommand::new(hash.as_bytes(), &cli.command).encode(&mut buf)?; |
||||||
|
sock.send(&buf[..n])?; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let cli = cli::parse(); |
||||||
|
|
||||||
|
if let Err(e) = send_command(&cli) { |
||||||
|
eprintln!("error: {}", e); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue