diff --git a/docs/turn.md b/docs/turn.md index 94d32db1..3db309cc 100644 --- a/docs/turn.md +++ b/docs/turn.md @@ -7,17 +7,18 @@ ## Edit/Add a few settings to your existing conduit.toml ``` +[turn] # Refer to your Coturn settings. # `your.turn.url` has to match the REALM setting of your Coturn as well as `transport`. -turn_uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"] +uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"] # static-auth-secret of your turnserver -turn_secret = "ADD SECRET HERE" +secret = "ADD SECRET HERE" # If you have your TURN server configured to use a username and password # you can provide these information too. In this case comment out `turn_secret above`! -#turn_username = "" -#turn_password = "" +#username = "" +#password = "" ``` ## Apply settings diff --git a/src/api/client_server/voip.rs b/src/api/client_server/voip.rs index f0d91f71..4a409a81 100644 --- a/src/api/client_server/voip.rs +++ b/src/api/client_server/voip.rs @@ -1,7 +1,10 @@ -use crate::{services, Result, Ruma}; +use crate::{config::TurnAuth, services, Error, Result, Ruma}; use base64::{engine::general_purpose, Engine as _}; use hmac::{Hmac, Mac}; -use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch}; +use ruma::{ + api::client::{error::ErrorKind, voip::get_turn_server_info}, + SecondsSinceUnixEpoch, +}; use sha1::Sha1; use std::time::{Duration, SystemTime}; @@ -9,40 +12,41 @@ type HmacSha1 = Hmac; /// # `GET /_matrix/client/r0/voip/turnServer` /// -/// TODO: Returns information about the recommended turn server. +/// Returns information about the recommended turn server. pub async fn turn_server_route( body: Ruma, ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - let turn_secret = services().globals.turn_secret().clone(); + if let Some(turn) = services().globals.turn() { + let (username, password) = match turn.auth { + TurnAuth::Secret { secret } => { + let expiry = SecondsSinceUnixEpoch::from_system_time( + SystemTime::now() + Duration::from_secs(turn.ttl), + ) + .expect("time is valid"); - let (username, password) = if !turn_secret.is_empty() { - let expiry = SecondsSinceUnixEpoch::from_system_time( - SystemTime::now() + Duration::from_secs(services().globals.turn_ttl()), - ) - .expect("time is valid"); + let username: String = format!("{}:{}", expiry.get(), sender_user); - let username: String = format!("{}:{}", expiry.get(), sender_user); + let mut mac = HmacSha1::new_from_slice(secret.as_bytes()) + .expect("HMAC can take key of any size"); + mac.update(username.as_bytes()); - let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes()) - .expect("HMAC can take key of any size"); - mac.update(username.as_bytes()); + let password: String = + general_purpose::STANDARD.encode(mac.finalize().into_bytes()); - let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes()); + (username, password) + } + TurnAuth::UserPass { username, password } => (username, password), + }; - (username, password) + Ok(get_turn_server_info::v3::Response { + username, + password, + uris: turn.uris, + ttl: Duration::from_secs(turn.ttl), + }) } else { - ( - services().globals.turn_username().clone(), - services().globals.turn_password().clone(), - ) - }; - - Ok(get_turn_server_info::v3::Response { - username, - password, - uris: services().globals.turn_uris().to_vec(), - ttl: Duration::from_secs(services().globals.turn_ttl()), - }) + Err(Error::BadRequest(ErrorKind::NotFound, "No TURN config set")) + } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 7cac2ed8..f87fd593 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -72,17 +72,15 @@ pub struct Config { pub trusted_servers: Vec, #[serde(default = "default_log")] pub log: String, - #[serde(default)] - pub turn_username: String, - #[serde(default)] - pub turn_password: String, - #[serde(default = "Vec::new")] - pub turn_uris: Vec, - #[serde(default)] - pub turn_secret: String, + pub turn_username: Option, + pub turn_password: Option, + pub turn_uris: Option>, + pub turn_secret: Option, #[serde(default = "default_turn_ttl")] pub turn_ttl: u64, + pub turn: Option, + pub emergency_password: Option, #[serde(flatten)] @@ -95,6 +93,22 @@ pub struct TlsConfig { pub key: String, } +#[derive(Clone, Debug, Deserialize)] +pub struct TurnConfig { + pub uris: Vec, + #[serde(default = "default_turn_ttl")] + pub ttl: u64, + #[serde(flatten)] + pub auth: TurnAuth, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum TurnAuth { + UserPass { username: String, password: String }, + Secret { secret: String }, +} + #[derive(Clone, Debug, Deserialize, Default)] pub struct WellKnownConfig { #[serde(rename = "well_known_client")] @@ -103,7 +117,14 @@ pub struct WellKnownConfig { pub server: Option, } -const DEPRECATED_KEYS: &[&str] = &["cache_capacity"]; +const DEPRECATED_KEYS: &[&str] = &[ + "cache_capacity", + "turn_username", + "turn_password", + "turn_uris", + "turn_secret", + "turn_ttl", +]; impl Config { pub fn warn_deprecated(&self) { @@ -146,6 +167,32 @@ impl Config { } } } + + pub fn turn(&self) -> Option { + if self.turn.is_some() { + self.turn.clone() + } else if let Some(uris) = self.turn_uris.clone() { + if let Some(secret) = self.turn_secret.clone() { + Some(TurnConfig { + uris, + ttl: self.turn_ttl, + auth: TurnAuth::Secret { secret }, + }) + } else if let (Some(username), Some(password)) = + (self.turn_username.clone(), self.turn_password.clone()) + { + Some(TurnConfig { + uris, + ttl: self.turn_ttl, + auth: TurnAuth::UserPass { username, password }, + }) + } else { + None + } + } else { + None + } + } } impl fmt::Display for Config { @@ -201,37 +248,18 @@ impl fmt::Display for Config { } &lst.join(", ") }), - ( - "TURN username", - if self.turn_username.is_empty() { - "not set" + ("TURN URIs", { + if let Some(turn) = self.turn() { + let mut lst = vec![]; + for item in turn.uris.iter().cloned().enumerate() { + let (_, uri): (usize, String) = item; + lst.push(uri); + } + &lst.join(", ") } else { - &self.turn_username - }, - ), - ("TURN password", { - if self.turn_password.is_empty() { - "not set" - } else { - "set" + "unset" } }), - ("TURN secret", { - if self.turn_secret.is_empty() { - "not set" - } else { - "set" - } - }), - ("Turn TTL", &self.turn_ttl.to_string()), - ("Turn URIs", { - let mut lst = vec![]; - for item in self.turn_uris.iter().cloned().enumerate() { - let (_, uri): (usize, String) = item; - lst.push(uri); - } - &lst.join(", ") - }), ("Well-known server name", well_known_server.as_str()), ("Well-known client URL", &self.well_known_client()), ]; diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 3325e518..acef484d 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -7,7 +7,7 @@ use ruma::{ use crate::api::server_server::DestinationResponse; -use crate::{services, Config, Error, Result}; +use crate::{config::TurnConfig, services, Config, Error, Result}; use futures_util::FutureExt; use hickory_resolver::TokioAsyncResolver; use hyper_util::client::legacy::connect::dns::{GaiResolver, Name as HyperName}; @@ -348,6 +348,10 @@ impl Service { &self.config.trusted_servers } + pub fn turn(&self) -> Option { + self.config.turn() + } + pub fn dns_resolver(&self) -> &TokioAsyncResolver { &self.dns_resolver } @@ -356,26 +360,6 @@ impl Service { self.jwt_decoding_key.as_ref() } - pub fn turn_password(&self) -> &String { - &self.config.turn_password - } - - pub fn turn_ttl(&self) -> u64 { - self.config.turn_ttl - } - - pub fn turn_uris(&self) -> &[String] { - &self.config.turn_uris - } - - pub fn turn_username(&self) -> &String { - &self.config.turn_username - } - - pub fn turn_secret(&self) -> &String { - &self.config.turn_secret - } - pub fn emergency_password(&self) -> &Option { &self.config.emergency_password }