Denis Drakhnia
1 year ago
6 changed files with 227 additions and 0 deletions
@ -0,0 +1,13 @@
@@ -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 @@
@@ -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 @@
@@ -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