diff --git a/src/main.rs b/src/main.rs index 23c793f..fa3be79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, + username: Option, + password: Option, + tokens: Option>, +} + +async fn test_explore_server(Query(options): Query) -> Result>, 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) -> Result, 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 { diff --git a/src/tcp/mod.rs b/src/tcp/mod.rs index 96ed685..48df93a 100644 --- a/src/tcp/mod.rs +++ b/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 { + 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 { + 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) { + 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, +} + +impl ControlChannel { + pub async fn new(host: &str, port: Option) -> std::io::Result { + 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 { + 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)?) + } }