From 734cf7a2a8cc65acc27598258f1df0b3cd270fed Mon Sep 17 00:00:00 2001 From: lemonsh Date: Mon, 1 May 2023 10:34:42 +0200 Subject: [PATCH] handle access denied --- connectbox/src/error.rs | 7 +++- connectbox/src/lib.rs | 28 ++++++++++---- connectbox/src/models.rs | 82 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 14 deletions(-) diff --git a/connectbox/src/error.rs b/connectbox/src/error.rs index d3a2cff..e734eba 100644 --- a/connectbox/src/error.rs +++ b/connectbox/src/error.rs @@ -1,4 +1,3 @@ -use reqwest::header::ToStrError; use thiserror::Error; /// The error type used globally by the library. @@ -12,11 +11,15 @@ pub enum Error { UnexpectedResponse(String), #[error("you are not logged in, or perhaps the session has expired")] NotAuthorized, + #[error("access denied, most likely someone else is already logged in")] + AccessDenied, + #[error("an unexpected redirection has occurred: {0:?}")] + UnexpectedRedirect(String), #[error(transparent)] URLParseError(#[from] url::ParseError), #[error(transparent)] - InvalidHeaderValue(#[from] ToStrError), + InvalidHeaderValue(#[from] reqwest::header::ToStrError), #[error(transparent)] HttpError(#[from] reqwest::Error), #[error(transparent)] diff --git a/connectbox/src/lib.rs b/connectbox/src/lib.rs index b0155f3..d6c18d9 100644 --- a/connectbox/src/lib.rs +++ b/connectbox/src/lib.rs @@ -6,7 +6,7 @@ pub use error::Error; use reqwest::{ cookie::{CookieStore, Jar}, redirect::Policy, - Client, Url, + Client, Url, header::HeaderValue, }; use serde::de::DeserializeOwned; @@ -15,7 +15,7 @@ mod functions; /// Data structures used by the library. pub mod models; -// A Result type based on the library's Error +/// A Result type based on the library's Error pub type Result = std::result::Result; type Field<'a, 'b> = (Cow<'a, str>, Cow<'b, str>); @@ -85,7 +85,7 @@ impl ConnectBox { if resp.status().is_redirection() { if self.auto_reauth && !reauthed { reauthed = true; - tracing::debug!("session has expired, attempting reauth"); + tracing::info!("session <{}> has expired, attempting reauth", self.cookie("SID")?.as_deref().unwrap_or("unknown")); self._login().await?; continue; } @@ -117,7 +117,7 @@ impl ConnectBox { if resp.status().is_redirection() { if self.auto_reauth && !reauthed { reauthed = true; - tracing::debug!("session has expired, attempting reauth"); + tracing::info!("session <{}> has expired, attempting reauth", self.cookie("SID")?.as_deref().unwrap_or("unknown")); self._login().await?; continue; } @@ -136,13 +136,25 @@ impl ConnectBox { ("Password".into(), (&self.code).into()), ]; let req = self.http.post(self.setter_url.clone()).form(&form); - let response = req.send().await?.text().await?; - if response == "idloginincorrect" { + let resp = req.send().await?; + if resp.status().is_redirection() { + if let Some(location) = resp.headers().get("Location").map(HeaderValue::to_str) { + let location = location?; + return if location == "../common_page/Access-denied.html" { + Err(Error::AccessDenied) + } else { + Err(Error::UnexpectedRedirect(location.to_string())) + } + } + } + let resp_text = resp.text().await?; + if resp_text == "idloginincorrect" { return Err(Error::IncorrectCode); } - let sid = response + let sid = resp_text .strip_prefix("successful;SID=") - .ok_or_else(|| Error::UnexpectedResponse(response.clone()))?; + .ok_or_else(|| Error::UnexpectedResponse(resp_text.clone()))?; + tracing::info!("session <{sid}>: logged in successfully"); self.cookie_store .add_cookie_str(&format!("SID={sid}"), &self.base_url); diff --git a/connectbox/src/models.rs b/connectbox/src/models.rs index f679a97..b312757 100644 --- a/connectbox/src/models.rs +++ b/connectbox/src/models.rs @@ -1,6 +1,7 @@ +use std::net::Ipv4Addr; use std::time::Duration; -use serde::de::Error; +use serde::de::{Error, self, Unexpected}; use serde::{Deserialize, Deserializer}; #[derive(Deserialize, Debug)] @@ -19,21 +20,94 @@ pub struct LanUserTable { #[derive(Deserialize, Debug)] pub struct ClientInfo { - pub index: u32, pub interface: String, - #[serde(rename = "interfaceid")] - pub interface_id: u32, #[serde(rename = "IPv4Addr")] pub ipv4_addr: String, + pub index: u32, + #[serde(rename = "interfaceid")] + pub interface_id: u32, pub hostname: String, #[serde(rename = "MACAddr")] pub mac: String, + pub method: u32, #[serde(rename = "leaseTime")] #[serde(deserialize_with = "deserialize_lease_time")] pub lease_time: Duration, pub speed: u32, } +#[derive(Deserialize, Debug)] +pub struct PortForwards { + #[serde(rename = "LanIP")] + pub lan_ip: Ipv4Addr, + #[serde(rename = "subnetmask")] + pub subnet_mask: Ipv4Addr, + #[serde(rename = "instance")] + #[serde(deserialize_with = "unwrap_xml_list")] + pub entries: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct PortForwardEntry { + pub id: u32, + #[serde(rename = "local_IP")] + pub local_ip: Ipv4Addr, + pub start_port: u16, + pub end_port: u16, + #[serde(rename = "start_portIn")] + pub start_port_in: u16, + #[serde(rename = "end_portIn")] + pub end_port_in: u16, + pub protocol: PortForwardProtocol, + #[serde(deserialize_with = "bool_from_int")] + pub enable: bool +} + +#[derive(Debug)] +pub enum PortForwardProtocol { + Tcp, + Udp, + Both, +} + +impl PortForwardProtocol { + fn id(&self) -> u8 { + match self { + PortForwardProtocol::Tcp => 1, + PortForwardProtocol::Udp => 2, + PortForwardProtocol::Both => 3, + } + } +} + +impl<'de> Deserialize<'de> for PortForwardProtocol { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + match u8::deserialize(d)? { + 1 => Ok(Self::Tcp), + 2 => Ok(Self::Udp), + 3 => Ok(Self::Both), + _ => Err(D::Error::custom("protocol not in range 1..=3")), + } + } +} + +fn bool_from_int<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + match u8::deserialize(deserializer)? { + 0 => Ok(false), + 1 => Ok(true), + other => Err(de::Error::invalid_value( + Unexpected::Unsigned(other as u64), + &"zero or one", + )), + } +} + #[derive(Deserialize)] struct List { #[serde(rename = "$value")]