diff --git a/CHANGELOG.md b/CHANGELOG.md index 6751cf1..67c4722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## `v0.3.3` (2021-10-20) + +* [#25](https://github.com/scottlamb/retina/issues/25): better HTTP + authentication support via the new [`http-auth` + crate](https://crates.io/crates/http-auth). Before, `retina` would only + authenticate properly if the first requested challenge was `Digest`. Now, it + will pick out a `Digest` or `Basic` challenge from a list. + ## `v0.3.2` (2021-09-29) * better `TEARDOWN` handling, which often avoids the need to wait for session diff --git a/Cargo.lock b/Cargo.lock index 94f34c3..87bf611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,19 +277,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "digest_auth" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c" -dependencies = [ - "digest", - "hex", - "md-5", - "rand", - "sha2", -] - [[package]] name = "either" version = "1.6.1" @@ -481,6 +468,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http-auth" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805afa6c41edf02ff4643e6672810974a83c2d866be06e0c377e1789084f6a7e" +dependencies = [ + "base64", + "digest", + "hex", + "md-5", + "memchr", + "rand", + "sha2", +] + [[package]] name = "idna" version = "0.2.3" @@ -587,9 +589,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" @@ -650,9 +652,9 @@ dependencies = [ [[package]] name = "nom" -version = "6.2.1" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", "funty", @@ -977,17 +979,17 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "retina" -version = "0.3.2" +version = "0.3.3" dependencies = [ "anyhow", "base64", "bitreader", "bytes", "criterion", - "digest_auth", "futures", "h264-reader", "hex", + "http-auth", "itertools", "log", "mylog", diff --git a/Cargo.toml b/Cargo.toml index bf2d7ef..02312f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "retina" -version = "0.3.2" +version = "0.3.3" authors = ["Scott Lamb "] license = "MIT/Apache-2.0" edition = "2018" @@ -14,10 +14,10 @@ include = ["src/**/*", "benches", "Cargo.toml"] base64 = "0.13.0" bitreader = "0.3.3" bytes = "1.0.1" -digest_auth = "0.3.0" futures = "0.3.14" -hex = "0.4.3" h264-reader = "0.5.0" +hex = "0.4.3" +http-auth = "0.1.2" log = "0.4.8" once_cell = "1.7.2" pin-project = "1.0.7" diff --git a/src/client/mod.rs b/src/client/mod.rs index 8a886be..47eb1d6 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,13 +1,14 @@ // Copyright (C) 2021 Scott Lamb // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::convert::TryFrom; use std::mem::MaybeUninit; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::num::NonZeroU32; use std::sync::{Arc, Mutex}; use std::task::Poll; use std::time::Instant; -use std::{borrow::Cow, fmt::Debug, num::NonZeroU16, pin::Pin}; +use std::{fmt::Debug, num::NonZeroU16, pin::Pin}; use self::channel_mapping::*; pub use self::timeline::Timeline; @@ -656,7 +657,7 @@ struct SessionInner { runtime_handle: Option, options: SessionOptions, - requested_auth: Option, + requested_auth: Option, presentation: Presentation, /// This will be set iff one or more `SETUP` calls have been issued. @@ -727,7 +728,7 @@ impl RtspConnection { &mut self, mode: ResponseMode, options: &SessionOptions, - requested_auth: &mut Option, + requested_auth: &mut Option, req: &mut rtsp_types::Request, ) -> Result<(RtspMessageContext, u32, rtsp_types::Response), Error> { loop { @@ -828,22 +829,6 @@ impl RtspConnection { }), Some(h) => h, }; - let www_authenticate = www_authenticate.as_str(); - if !www_authenticate.starts_with("Digest ") { - // TODO: the header(s) might also indicate both Basic and Digest; we shouldn't - // error or not based on ordering. - bail!(ErrorInt::RtspResponseError { - conn_ctx: *self.inner.ctx(), - msg_ctx, - method: req.method().clone(), - cseq, - status: resp.status(), - description: format!( - "Non-digest authentication requested: {}", - www_authenticate - ), - }) - } if options.creds.is_none() { bail!(ErrorInt::RtspResponseError { conn_ctx: *self.inner.ctx(), @@ -855,21 +840,18 @@ impl RtspConnection { .to_owned(), }) } - let www_authenticate = digest_auth::WwwAuthenticateHeader::parse(www_authenticate) - .map_err(|e| { - wrap!(ErrorInt::RtspResponseError { - conn_ctx: *self.inner.ctx(), - msg_ctx, - method: req.method().clone(), - cseq, - status: resp.status(), - description: format!( - "Bad WWW-Authenticate header {:?}: {}", - www_authenticate, e - ), - }) - })?; - *requested_auth = Some(www_authenticate); + let www_authenticate = www_authenticate.as_str(); + *requested_auth = match http_auth::PasswordClient::try_from(www_authenticate) { + Ok(c) => Some(c), + Err(e) => bail!(ErrorInt::RtspResponseError { + conn_ctx: *self.inner.ctx(), + msg_ctx, + method: req.method().clone(), + cseq, + status: resp.status(), + description: format!("Can't understand WWW-Authenticate header: {}", e), + }), + }; continue; } else if !resp.status().is_success() { bail!(ErrorInt::RtspResponseError { @@ -889,7 +871,7 @@ impl RtspConnection { fn fill_req( &mut self, options: &SessionOptions, - requested_auth: &mut Option, + requested_auth: &mut Option, req: &mut rtsp_types::Request, ) -> Result { let cseq = self.next_cseq; @@ -899,21 +881,15 @@ impl RtspConnection { .creds .as_ref() .expect("creds were checked when filling request_auth"); - let uri = req.request_uri().map(|u| u.as_str()).unwrap_or("*"); - let method = digest_auth::HttpMethod(Cow::Borrowed(req.method().into())); - let ctx = digest_auth::AuthContext::new_with_method( - &creds.username, - &creds.password, - uri, - Option::<&'static [u8]>::None, - method, - ); - - // digest_auth's comments seem to say 'respond' failing means a parser bug. let authorization = auth - .respond(&ctx) - .map_err(|e| wrap!(ErrorInt::Internal(e.into())))? - .to_string(); + .respond(&http_auth::PasswordParams { + username: &creds.username, + password: &creds.password, + uri: req.request_uri().map(|u| u.as_str()).unwrap_or("*"), + method: req.method().into(), + body: Some(&[]), + }) + .map_err(|e| wrap!(ErrorInt::Internal(e.into())))?; req.insert_header(rtsp_types::headers::AUTHORIZATION, authorization); } req.insert_header(rtsp_types::headers::CSEQ, cseq.to_string()); diff --git a/src/client/teardown.rs b/src/client/teardown.rs index 97a621f..1f5bda4 100644 --- a/src/client/teardown.rs +++ b/src/client/teardown.rs @@ -19,7 +19,7 @@ pub(super) async fn background_teardown( base_url: Url, session_id: Box, options: SessionOptions, - requested_auth: Option, + requested_auth: Option, conn: Option, mut tx: tokio::sync::watch::Sender>>, expires: tokio::time::Instant, @@ -63,7 +63,7 @@ pub(super) async fn teardown_loop_forever( url: Url, session_id: &str, options: &SessionOptions, - mut requested_auth: Option, + mut requested_auth: Option, mut conn: Option, tx: &mut tokio::sync::watch::Sender>>, ) { @@ -155,7 +155,7 @@ pub(super) async fn teardown_loop_forever( async fn attempt( req: &mut Request, options: &SessionOptions, - requested_auth: &mut Option, + requested_auth: &mut Option, mut conn: RtspConnection, ) -> Result { let e = match conn