mirror of
https://git.alemi.dev/mumble-stats-api.git
synced 2024-11-22 16:04:49 +01:00
feat: mumble control channel, simple userlist route
This commit is contained in:
parent
2f4b52e09d
commit
4daacc9712
2 changed files with 193 additions and 4 deletions
57
src/main.rs
57
src/main.rs
|
@ -5,7 +5,10 @@ use clap::Parser;
|
||||||
use axum::{extract::Query, routing::get, Json, Router};
|
use axum::{extract::Query, routing::get, Json, Router};
|
||||||
use mumble::{ServerInfo, ping_mumble_server};
|
use mumble::{ServerInfo, ping_mumble_server};
|
||||||
|
|
||||||
mod proto;
|
use crate::tcp::ControlChannel;
|
||||||
|
|
||||||
|
mod tcp;
|
||||||
|
mod udp;
|
||||||
mod mumble;
|
mod mumble;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -19,10 +22,56 @@ async fn main() {
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
let app = Router::new()
|
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();
|
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)]
|
#[derive(serde::Deserialize)]
|
||||||
|
@ -33,7 +82,7 @@ struct PingOptions {
|
||||||
|
|
||||||
async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<ServerInfo>, String> {
|
async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<ServerInfo>, String> {
|
||||||
let tuple = (options.host, options.port.unwrap_or(64738));
|
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}")),
|
Err(e) => Err(format!("invalid address: {e}")),
|
||||||
Ok(addrs) => {
|
Ok(addrs) => {
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
|
|
140
src/tcp/mod.rs
140
src/tcp/mod.rs
|
@ -1,3 +1,143 @@
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
|
||||||
|
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream};
|
||||||
|
use tokio_native_tls::TlsStream;
|
||||||
|
|
||||||
pub mod proto {
|
pub mod proto {
|
||||||
|
use prost::Message;
|
||||||
|
|
||||||
tonic::include_proto!("mumble");
|
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)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue