Skip to content

Commit

Permalink
Improved OTP listener, call signout correctly in standalone mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ancwrd1 committed Feb 8, 2025
1 parent fd26f06 commit 449d36b
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 44 deletions.
25 changes: 13 additions & 12 deletions snx-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,24 @@ use std::{collections::VecDeque, future::Future, sync::Arc};

use clap::Parser;
use futures::pin_mut;
use tokio::{
signal::unix,
sync::{mpsc, oneshot},
};
use tokio::{signal::unix, sync::mpsc};
use tracing::{debug, metadata::LevelFilter, warn};

use crate::cmdline::CmdlineParams;
use snxcore::model::params::TunnelType;
use snxcore::{
browser::run_otp_listener,
browser::spawn_otp_listener,
ccc::CccHttpClient,
model::{
params::{OperationMode, TunnelParams},
MfaType, SessionState,
},
platform,
prompt::{SecurePrompt, TtyPrompt, OTP_TIMEOUT},
prompt::{SecurePrompt, TtyPrompt},
server::CommandServer,
server_info, tunnel,
};

use crate::cmdline::CmdlineParams;

mod cmdline;

fn is_root() -> bool {
Expand Down Expand Up @@ -161,9 +158,8 @@ async fn main_standalone(params: TunnelParams) -> anyhow::Result<()> {
MfaType::SamlSso => {
println!("For SAML authentication open the following URL in your browser:");
println!("{}", challenge.prompt);
let (tx, rx) = oneshot::channel();
tokio::spawn(run_otp_listener(tx));
let otp = tokio::time::timeout(OTP_TIMEOUT, rx).await??;
let receiver = spawn_otp_listener();
let otp = receiver.await??;
session = connector.challenge_code(session, &otp).await?;
}
MfaType::UserNameInput => {
Expand All @@ -173,7 +169,7 @@ async fn main_standalone(params: TunnelParams) -> anyhow::Result<()> {
}
}

let tunnel = connector.create_tunnel(session, command_sender).await?;
let tunnel = connector.create_tunnel(session.clone(), command_sender).await?;

if let Err(e) = platform::start_network_state_monitoring().await {
warn!("Unable to start network monitoring: {}", e);
Expand All @@ -198,6 +194,11 @@ async fn main_standalone(params: TunnelParams) -> anyhow::Result<()> {
}
}
result = &mut tunnel_fut => {
if params.tunnel_type == TunnelType::Ssl || !params.ike_persist {
debug!("Signing out");
let client = CccHttpClient::new(params.clone(), Some(session));
let _ = client.signout().await;
}
break result;
}
}
Expand Down
51 changes: 33 additions & 18 deletions snxcore/src/browser.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use anyhow::anyhow;
use once_cell::sync::Lazy;
use regex::Regex;
Expand All @@ -7,6 +9,8 @@ use tokio::{
sync::oneshot,
};

const OTP_TIMEOUT: Duration = Duration::from_secs(120);

pub trait BrowserController {
fn open(&self, url: &str) -> anyhow::Result<()>;
fn close(&self);
Expand All @@ -22,30 +26,41 @@ impl BrowserController for SystemBrowser {
fn close(&self) {}
}

pub async fn run_otp_listener(sender: oneshot::Sender<String>) -> anyhow::Result<()> {
pub fn spawn_otp_listener() -> oneshot::Receiver<anyhow::Result<String>> {
static OTP_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^GET /(?<otp>[0-9a-f]{60}|[0-9A-F]{60}).*").unwrap());

let tcp = TcpListener::bind("127.0.0.1:7779").await?;
let (mut stream, _) = tcp.accept().await?;
let (sender, receiver) = oneshot::channel();

let mut buf = [0u8; 65];
stream.read_exact(&mut buf).await?;
let fut = async move {
let tcp = TcpListener::bind("127.0.0.1:7779").await?;
let (mut stream, _) = tcp.accept().await?;

let mut data = String::from_utf8_lossy(&buf).into_owned();
let mut buf = [0u8; 65];
stream.read_exact(&mut buf).await?;

while stream.read(&mut buf[0..1]).await.is_ok() && buf[0] != b'\n' && buf[0] != b'\r' {
data.push(buf[0].into());
}
let mut data = String::from_utf8_lossy(&buf).into_owned();

while stream.read(&mut buf[0..1]).await.is_ok() && buf[0] != b'\n' && buf[0] != b'\r' {
data.push(buf[0].into());
}

let _ = stream.shutdown().await;
drop(stream);
drop(tcp);
let _ = stream.shutdown().await;

if let Some(captures) = OTP_RE.captures(&data) {
if let Some(otp) = captures.name("otp") {
let _ = sender.send(otp.as_str().to_owned());
return Ok(());
if let Some(captures) = OTP_RE.captures(&data) {
if let Some(otp) = captures.name("otp") {
return Ok(otp.as_str().to_owned());
}
}
}
Err(anyhow!("No OTP acquired!"))
Err(anyhow!("Invalid OTP reply"))
};

tokio::spawn(async move {
let result = tokio::time::timeout(OTP_TIMEOUT, fut)
.await
.unwrap_or_else(|e| Err(e.into()));

let _ = sender.send(result);
});

receiver
}
14 changes: 6 additions & 8 deletions snxcore/src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use std::{collections::VecDeque, str::FromStr, sync::Arc, time::Duration};

use anyhow::anyhow;
use tokio::sync::oneshot;
use tracing::warn;

use crate::{
browser::{run_otp_listener, BrowserController},
browser::{spawn_otp_listener, BrowserController},
ccc::CccHttpClient,
model::{
params::TunnelParams, ConnectionStatus, MfaChallenge, MfaType, TunnelServiceRequest, TunnelServiceResponse,
},
platform::{self, UdpSocketExt},
prompt::{SecurePrompt, OTP_TIMEOUT},
prompt::SecurePrompt,
server_info,
};

Expand Down Expand Up @@ -148,18 +147,17 @@ where
result
}
MfaType::SamlSso => {
let (tx, rx) = oneshot::channel();
tokio::spawn(run_otp_listener(tx));
let receiver = spawn_otp_listener();

self.browser_controller.open(&mfa.prompt)?;

match tokio::time::timeout(OTP_TIMEOUT, rx).await {
match receiver.await {
Ok(Ok(otp)) => {
self.browser_controller.close();
Ok(otp)
}
_ => {
warn!("Unable to acquire OTP from the browser");
other => {
warn!("Unable to acquire OTP from the browser: {:?}", other);
Err(anyhow!("Unable to acquire OTP from the browser!"))
}
}
Expand Down
7 changes: 1 addition & 6 deletions snxcore/src/prompt.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use anyhow::anyhow;
use std::io::Write;
use std::{
io::{stderr, stdin, IsTerminal},
time::Duration,
};

pub const OTP_TIMEOUT: Duration = Duration::from_secs(120);
use std::io::{stderr, stdin, IsTerminal};

pub trait SecurePrompt {
fn get_secure_input(&self, prompt: &str) -> anyhow::Result<String>;
Expand Down

0 comments on commit 449d36b

Please sign in to comment.