pub mod codec;
use futures::sink::SinkExt;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/mumble_proto.rs"));
}
pub mod udp {
include!(concat!(env!("OUT_DIR"), "/mumble_udp.rs"));
}
use crate::codec::MumbleTcpCodec;
use crate::proto::*;
use rustls_pki_types::{CertificateDer, ServerName, UnixTime};
use std::fmt::{Debug, Formatter};
use std::net::Ipv4Addr;
use std::sync::Arc;
use tokio::net::{TcpStream, UdpSocket};
use tokio_rustls::client::TlsStream;
use tokio_rustls::rustls::client::danger::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
};
use tokio_rustls::rustls::{
ClientConfig, DigitallySignedStruct, Error, RootCertStore, SignatureScheme,
};
use tokio_rustls::TlsConnector;
use tokio_util::codec::Framed;
#[derive(Debug)]
pub enum MumbleMessage {
Version { data: Version },
UdpTunnel { data: UdpTunnel },
Ping { data: Ping },
Reject { data: Reject },
Authenticate { data: Authenticate },
ServerSync { data: ServerSync },
ChannelRemove { data: ChannelRemove },
ChannelState { data: ChannelState },
UserRemove { data: UserRemove },
UserState { data: UserState },
BanList { data: BanList },
TextMessage { data: TextMessage },
PermissionDenied { data: PermissionDenied },
Acl { data: Acl },
QueryUsers { data: QueryUsers },
CryptSetup { data: CryptSetup },
ContextActionModify { data: ContextActionModify },
ContextAction { data: ContextAction },
UserList { data: UserList },
VoiceTarget { data: VoiceTarget },
PermissionQuery { data: PermissionQuery },
CodecVersion { data: CodecVersion },
UserStats { data: UserStats },
RequestBlob { data: RequestBlob },
ServerConfig { data: ServerConfig },
SuggestConfig { data: SuggestConfig },
}
pub struct MumbleClient {
host: String,
name: String,
port: u16,
password: Option<String>,
}
pub(crate) struct NoVerifier;
impl Debug for NoVerifier {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "NoVerifier")
}
}
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
impl MumbleClient {
pub fn new(host: String, port: Option<u16>, name: String, password: Option<String>) -> Self {
Self {
host,
port: port.unwrap_or(64738),
name,
password,
}
}
async fn create_tcp_connection(&mut self) -> anyhow::Result<TlsStream<TcpStream>> {
let mut root_cert_store = RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
config
.dangerous()
.set_certificate_verifier(Arc::new(NoVerifier));
let connector = TlsConnector::from(Arc::new(config));
let dnsname = ServerName::try_from(self.host.clone())?;
let stream = TcpStream::connect((self.host.clone(), self.port)).await?;
Ok(connector.connect(dnsname, stream).await?)
}
pub async fn create_udp_connection(&mut self) -> anyhow::Result<UdpSocket> {
let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
sock.connect((self.host.clone(), self.port)).await?;
Ok(sock)
}
pub async fn connect(
&mut self,
) -> anyhow::Result<Framed<TlsStream<TcpStream>, MumbleTcpCodec>> {
let mut framed = Framed::new(self.create_tcp_connection().await?, MumbleTcpCodec::new());
let version_v1 = (1u16 as u32) << 16 | (5u8 as u32) << 8 | (1u8 as u32);
let version_v2 = 1u64 << 48 | 5u64 << 32 | 0u64 << 16 | 1u64;
framed
.send(MumbleMessage::Version {
data: Version {
os: Some(String::from("Linux")),
os_version: Some(String::from("os version")),
release: Some(String::from("release")),
version_v1: Some(version_v1),
version_v2: Some(version_v2),
},
})
.await?;
framed
.send(MumbleMessage::Authenticate {
data: Authenticate {
username: Some(self.name.clone()),
password: self.password.clone(),
tokens: vec![],
celt_versions: vec![],
opus: Some(true),
client_type: Some(1),
},
})
.await?;
Ok(framed)
}
}