feat: mumble control channel, simple userlist route

This commit is contained in:
əlemi 2024-02-18 23:58:46 +01:00
parent 2f4b52e09d
commit 4daacc9712
2 changed files with 193 additions and 4 deletions

View file

@ -5,7 +5,10 @@ use clap::Parser;
use axum::{extract::Query, routing::get, Json, Router};
use mumble::{ServerInfo, ping_mumble_server};
mod proto;
use crate::tcp::ControlChannel;
mod tcp;
mod udp;
mod mumble;
#[derive(Parser)]
@ -19,10 +22,56 @@ async fn main() {
// build our application with a route
let app = Router::new()
.route("/ping", get(ping_server));
.route("/ping", get(ping_server))
.route("/join", get(test_explore_server));
tracing::info!("serving mumble-stats-api");
let listener = tokio::net::TcpListener::bind("127.0.0.1:57039").await.unwrap();
axum::serve(listener, app).await.unwrap();
axum::serve(listener, app).await.unwrap()
}
#[derive(serde::Deserialize)]
struct ExploreOptions {
host: String,
port: Option<u16>,
username: Option<String>,
password: Option<String>,
tokens: Option<Vec<String>>,
}
async fn test_explore_server(Query(options): Query<ExploreOptions>) -> Result<Json<Vec<String>>, String> {
let mut channel = ControlChannel::new(&options.host, options.port).await.unwrap();
let version = tcp::proto::Version {
version_v1: None, // Some(67071),
version_v2: Some(281496485429248),
release: Some("1.5.517".into()),
os: None,
os_version: None,
};
let authenticate = tcp::proto::Authenticate {
username: Some(options.username.unwrap_or_else(|| ".mumble-stats-api".to_string())),
password: options.password,
tokens: options.tokens.clone().unwrap_or_else(|| Vec::new()),
celt_versions: vec![],
opus: Some(true),
client_type: Some(1),
};
channel.send(tcp::proto::Packet::Version(version)).await.unwrap();
channel.send(tcp::proto::Packet::Authenticate(authenticate)).await.unwrap();
let mut users = Vec::new();
loop {
match channel.recv().await {
Err(e) => break Err(format!("error receiving from server: {e}")),
// Ok(tcp::proto::Packet::TextMessage(msg)) => tracing::info!("{}", msg.message),
// Ok(tcp::proto::Packet::ChannelState(channel)) => tracing::info!("discovered channel: {:?}", channel.name),
Ok(tcp::proto::Packet::UserState(user)) => users.push(user.name.as_deref().unwrap_or("???").to_string()),
Ok(tcp::proto::Packet::ServerSync(_sync)) => break Ok(Json(users)),
Ok(pkt) => tracing::debug!("ignoring packet {:#?}", pkt),
}
}
}
#[derive(serde::Deserialize)]
@ -33,7 +82,7 @@ struct PingOptions {
async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<ServerInfo>, String> {
let tuple = (options.host, options.port.unwrap_or(64738));
match tuple.to_socket_addrs() {
match tuple.to_socket_addrs() { // TODO do it manually so we have control
Err(e) => Err(format!("invalid address: {e}")),
Ok(addrs) => {
for addr in addrs {

View file

@ -1,3 +1,143 @@
use std::net::ToSocketAddrs;
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream};
use tokio_native_tls::TlsStream;
pub mod proto {
use prost::Message;
tonic::include_proto!("mumble");
#[derive(Debug)]
pub enum Packet {
Version(Version),
#[allow(unused)] UDPTunnel(UdpTunnel),
Authenticate(Authenticate),
Ping(Ping),
Reject(Reject),
ServerSync(ServerSync),
ChannelRemove(ChannelRemove),
ChannelState(ChannelState),
UserRemove(UserRemove),
UserState(UserState),
BanList(BanList),
TextMessage(TextMessage),
PermissionDenied(PermissionDenied),
ACL(Acl),
QueryUsers(QueryUsers),
CryptSetup(CryptSetup),
ContextActionModify(ContextActionModify),
ContextAction(ContextAction),
UserList(UserList),
VoiceTarget(VoiceTarget),
PermissionQuery(PermissionQuery),
CodecVersion(CodecVersion),
UserStats(UserStats),
RequestBlob(RequestBlob),
ServerConfig(ServerConfig),
SuggestConfig(SuggestConfig),
}
impl Packet {
pub fn decode(id: u16, buffer: &[u8]) -> std::io::Result<Self> {
match id {
0 => Ok(Packet::Version(Version::decode(buffer)?)),
1 => todo!(),
2 => Ok(Packet::Authenticate(Authenticate::decode(buffer)?)),
3 => Ok(Packet::Ping(Ping::decode(buffer)?)),
4 => Ok(Packet::Reject(Reject::decode(buffer)?)),
5 => Ok(Packet::ServerSync(ServerSync::decode(buffer)?)),
6 => Ok(Packet::ChannelRemove(ChannelRemove::decode(buffer)?)),
7 => Ok(Packet::ChannelState(ChannelState::decode(buffer)?)),
8 => Ok(Packet::UserRemove(UserRemove::decode(buffer)?)),
9 => Ok(Packet::UserState(UserState::decode(buffer)?)),
10 => Ok(Packet::BanList(BanList::decode(buffer)?)),
11 => Ok(Packet::TextMessage(TextMessage::decode(buffer)?)),
12 => Ok(Packet::PermissionDenied(PermissionDenied::decode(buffer)?)),
13 => Ok(Packet::ACL(Acl::decode(buffer)?)),
14 => Ok(Packet::QueryUsers(QueryUsers::decode(buffer)?)),
15 => Ok(Packet::CryptSetup(CryptSetup::decode(buffer)?)),
16 => Ok(Packet::ContextActionModify(ContextActionModify::decode(buffer)?)),
17 => Ok(Packet::ContextAction(ContextAction::decode(buffer)?)),
18 => Ok(Packet::UserList(UserList::decode(buffer)?)),
19 => Ok(Packet::VoiceTarget(VoiceTarget::decode(buffer)?)),
20 => Ok(Packet::PermissionQuery(PermissionQuery::decode(buffer)?)),
21 => Ok(Packet::CodecVersion(CodecVersion::decode(buffer)?)),
22 => Ok(Packet::UserStats(UserStats::decode(buffer)?)),
23 => Ok(Packet::RequestBlob(RequestBlob::decode(buffer)?)),
24 => Ok(Packet::ServerConfig(ServerConfig::decode(buffer)?)),
25 => Ok(Packet::SuggestConfig(SuggestConfig::decode(buffer)?)),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidData)),
}
}
pub fn encode(self) -> (u16, Vec<u8>) {
match self {
Packet::Version(x) => (0, x.encode_to_vec()),
Packet::UDPTunnel(_) => todo!(),
Packet::Authenticate(x) => (2, x.encode_to_vec()),
Packet::Ping(x) => (3, x.encode_to_vec()),
Packet::Reject(x) => (4, x.encode_to_vec()),
Packet::ServerSync(x) => (5, x.encode_to_vec()),
Packet::ChannelRemove(x) => (6, x.encode_to_vec()),
Packet::ChannelState(x) => (7, x.encode_to_vec()),
Packet::UserRemove(x) => (8, x.encode_to_vec()),
Packet::UserState(x) => (9, x.encode_to_vec()),
Packet::BanList(x) => (10, x.encode_to_vec()),
Packet::TextMessage(x) => (11, x.encode_to_vec()),
Packet::PermissionDenied(x) => (12, x.encode_to_vec()),
Packet::ACL(x) => (13, x.encode_to_vec()),
Packet::QueryUsers(x) => (14, x.encode_to_vec()),
Packet::CryptSetup(x) => (15, x.encode_to_vec()),
Packet::ContextActionModify(x) => (16, x.encode_to_vec()),
Packet::ContextAction(x) => (17, x.encode_to_vec()),
Packet::UserList(x) => (18, x.encode_to_vec()),
Packet::VoiceTarget(x) => (19, x.encode_to_vec()),
Packet::PermissionQuery(x) => (20, x.encode_to_vec()),
Packet::CodecVersion(x) => (21, x.encode_to_vec()),
Packet::UserStats(x) => (22, x.encode_to_vec()),
Packet::RequestBlob(x) => (23, x.encode_to_vec()),
Packet::ServerConfig(x) => (24, x.encode_to_vec()),
Packet::SuggestConfig(x) => (25, x.encode_to_vec()),
}
}
}
}
pub struct ControlChannel {
stream: TlsStream<TcpStream>,
}
impl ControlChannel {
pub async fn new(host: &str, port: Option<u16>) -> std::io::Result<Self> {
let addr = (host, port.unwrap_or(64738)).to_socket_addrs()?
.filter(|a| a.is_ipv4())
.next()
.ok_or(std::io::Error::from(std::io::ErrorKind::AddrNotAvailable))?;
let socket = TcpStream::connect(addr).await?;
// use native_tls builder and then .into() so we can pass options to the builder
let connector : tokio_native_tls::TlsConnector = native_tls::TlsConnector::builder()
.danger_accept_invalid_certs(true)
.build()
.expect("could not create TLS connector").into();
let stream = connector.connect(host, socket).await.unwrap();
Ok(ControlChannel { stream })
}
pub async fn send(&mut self, pkt: proto::Packet) -> std::io::Result<()> {
let (id, buffer) = pkt.encode();
self.stream.write_u16(id).await?;
self.stream.write_u32(buffer.len() as u32).await?;
self.stream.write_all(&buffer).await?;
self.stream.flush().await?;
Ok(())
}
pub async fn recv(&mut self) -> std::io::Result<proto::Packet> {
let id = self.stream.read_u16().await?;
let size = self.stream.read_u32().await?;
let mut buffer = vec![0u8; size as usize];
self.stream.read_exact(&mut buffer).await?;
Ok(proto::Packet::decode(id, &buffer)?)
}
}