mumble-rs 0.1.1

A small simple crate to communicate with a mumble server
Documentation
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)
    }
}