Ensure vpnc_script and csd_wrapper executable

This commit is contained in:
Kevin Yue 2024-03-16 21:06:49 +08:00
parent a00f6a8cba
commit d5d92cfbee
10 changed files with 128 additions and 46 deletions

9
Cargo.lock generated
View File

@ -562,6 +562,13 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "common"
version = "2.1.0"
dependencies = [
"is_executable",
]
[[package]] [[package]]
name = "compile-time" name = "compile-time"
version = "0.2.0" version = "0.2.0"
@ -2522,7 +2529,7 @@ name = "openconnect"
version = "2.1.0" version = "2.1.0"
dependencies = [ dependencies = [
"cc", "cc",
"is_executable", "common",
"log", "log",
] ]

View File

@ -158,12 +158,12 @@ impl<'a> ConnectHandler<'a> {
let mtu = self.args.mtu.unwrap_or(0); let mtu = self.args.mtu.unwrap_or(0);
let vpn = Vpn::builder(gateway, cookie) let vpn = Vpn::builder(gateway, cookie)
.user_agent(self.args.user_agent.clone())
.script(self.args.script.clone()) .script(self.args.script.clone())
.user_agent(self.args.user_agent.clone())
.csd_uid(csd_uid) .csd_uid(csd_uid)
.csd_wrapper(self.args.csd_wrapper.clone()) .csd_wrapper(self.args.csd_wrapper.clone())
.mtu(mtu) .mtu(mtu)
.build(); .build()?;
let vpn = Arc::new(vpn); let vpn = Arc::new(vpn);
let vpn_clone = vpn.clone(); let vpn_clone = vpn.clone();

View File

@ -4,7 +4,7 @@ use gpapi::service::{
request::{ConnectRequest, WsRequest}, request::{ConnectRequest, WsRequest},
vpn_state::VpnState, vpn_state::VpnState,
}; };
use log::info; use log::{info, warn};
use openconnect::Vpn; use openconnect::Vpn;
use tokio::sync::{mpsc, oneshot, watch, RwLock}; use tokio::sync::{mpsc, oneshot, watch, RwLock};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -31,22 +31,29 @@ impl VpnTaskContext {
return; return;
} }
let vpn_state_tx = self.vpn_state_tx.clone();
let info = req.info().clone(); let info = req.info().clone();
let vpn_handle = Arc::clone(&self.vpn_handle); let vpn_handle = Arc::clone(&self.vpn_handle);
let args = req.args(); let args = req.args();
let vpn = Vpn::builder(req.gateway().server(), args.cookie()) let vpn = match Vpn::builder(req.gateway().server(), args.cookie())
.user_agent(args.user_agent())
.script(args.vpnc_script()) .script(args.vpnc_script())
.user_agent(args.user_agent())
.csd_uid(args.csd_uid()) .csd_uid(args.csd_uid())
.csd_wrapper(args.csd_wrapper()) .csd_wrapper(args.csd_wrapper())
.mtu(args.mtu()) .mtu(args.mtu())
.os(args.openconnect_os()) .os(args.openconnect_os())
.build(); .build()
{
Ok(vpn) => vpn,
Err(err) => {
warn!("Failed to create VPN: {}", err);
vpn_state_tx.send(VpnState::Disconnected).ok();
return;
}
};
// Save the VPN handle // Save the VPN handle
vpn_handle.write().await.replace(vpn); vpn_handle.write().await.replace(vpn);
let vpn_state_tx = self.vpn_state_tx.clone();
let connect_info = Box::new(info.clone()); let connect_info = Box::new(info.clone());
vpn_state_tx.send(VpnState::Connecting(connect_info)).ok(); vpn_state_tx.send(VpnState::Connecting(connect_info)).ok();

11
crates/common/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "common"
rust-version.workspace = true
version.workspace = true
authors.workspace = true
homepage.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
is_executable.workspace = true

1
crates/common/src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod vpn_utils;

View File

@ -0,0 +1,41 @@
use is_executable::IsExecutable;
use std::path::Path;
pub use is_executable::is_executable;
const VPNC_SCRIPT_LOCATIONS: [&str; 6] = [
"/usr/local/share/vpnc-scripts/vpnc-script",
"/usr/local/sbin/vpnc-script",
"/usr/share/vpnc-scripts/vpnc-script",
"/usr/sbin/vpnc-script",
"/etc/vpnc/vpnc-script",
"/etc/openconnect/vpnc-script",
];
const CSD_WRAPPER_LOCATIONS: [&str; 3] = [
#[cfg(target_arch = "x86_64")]
"/usr/lib/x86_64-linux-gnu/openconnect/hipreport.sh",
#[cfg(target_arch = "aarch64")]
"/usr/lib/aarch64-linux-gnu/openconnect/hipreport.sh",
"/usr/lib/openconnect/hipreport.sh",
"/usr/libexec/openconnect/hipreport.sh",
];
fn find_executable(locations: &[&str]) -> Option<String> {
for location in locations.iter() {
let path = Path::new(location);
if path.is_executable() {
return Some(location.to_string());
}
}
None
}
pub fn find_vpnc_script() -> Option<String> {
find_executable(&VPNC_SCRIPT_LOCATIONS)
}
pub fn find_csd_wrapper() -> Option<String> {
find_executable(&CSD_WRAPPER_LOCATIONS)
}

View File

@ -6,8 +6,8 @@ license.workspace = true
links = "openconnect" links = "openconnect"
[dependencies] [dependencies]
common = { path = "../common" }
log.workspace = true log.workspace = true
is_executable.workspace = true
[build-dependencies] [build-dependencies]
cc = "1" cc = "1"

View File

@ -1,5 +1,4 @@
mod ffi; mod ffi;
mod vpn; mod vpn;
mod vpnc_script;
pub use vpn::*; pub use vpn::*;

View File

@ -1,11 +1,13 @@
use std::{ use std::{
ffi::{c_char, CString}, ffi::{c_char, CString},
fmt,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use common::vpn_utils::{find_vpnc_script, is_executable};
use log::info; use log::info;
use crate::{ffi, vpnc_script::find_default_vpnc_script}; use crate::ffi;
type OnConnectedCallback = Arc<RwLock<Option<Box<dyn FnOnce() + 'static + Send + Sync>>>>; type OnConnectedCallback = Arc<RwLock<Option<Box<dyn FnOnce() + 'static + Send + Sync>>>>;
@ -77,11 +79,31 @@ impl Vpn {
} }
} }
#[derive(Debug)]
pub struct VpnError<'a> {
message: &'a str,
}
impl<'a> VpnError<'a> {
fn new(message: &'a str) -> Self {
Self { message }
}
}
impl fmt::Display for VpnError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for VpnError<'_> {}
pub struct VpnBuilder { pub struct VpnBuilder {
server: String, server: String,
cookie: String, cookie: String,
user_agent: Option<String>,
script: Option<String>, script: Option<String>,
user_agent: Option<String>,
os: Option<String>, os: Option<String>,
csd_uid: u32, csd_uid: u32,
@ -95,22 +117,25 @@ impl VpnBuilder {
Self { Self {
server: server.to_string(), server: server.to_string(),
cookie: cookie.to_string(), cookie: cookie.to_string(),
user_agent: None,
script: None, script: None,
user_agent: None,
os: None, os: None,
csd_uid: 0, csd_uid: 0,
csd_wrapper: None, csd_wrapper: None,
mtu: 0, mtu: 0,
} }
} }
pub fn user_agent<T: Into<Option<String>>>(mut self, user_agent: T) -> Self { pub fn script<T: Into<Option<String>>>(mut self, script: T) -> Self {
self.user_agent = user_agent.into(); self.script = script.into();
self self
} }
pub fn script<T: Into<Option<String>>>(mut self, script: T) -> Self { pub fn user_agent<T: Into<Option<String>>>(mut self, user_agent: T) -> Self {
self.script = script.into(); self.user_agent = user_agent.into();
self self
} }
@ -134,12 +159,27 @@ impl VpnBuilder {
self self
} }
pub fn build(self) -> Vpn { pub fn build(self) -> Result<Vpn, VpnError<'static>> {
let script = match self.script {
Some(script) => {
if !is_executable(&script) {
return Err(VpnError::new("vpnc script is not executable"));
}
script
}
None => find_vpnc_script().ok_or_else(|| VpnError::new("Failed to find vpnc-script"))?,
};
if let Some(csd_wrapper) = &self.csd_wrapper {
if !is_executable(csd_wrapper) {
return Err(VpnError::new("CSD wrapper is not executable"));
}
}
let user_agent = self.user_agent.unwrap_or_default(); let user_agent = self.user_agent.unwrap_or_default();
let script = self.script.or_else(find_default_vpnc_script).unwrap_or_default();
let os = self.os.unwrap_or("linux".to_string()); let os = self.os.unwrap_or("linux".to_string());
Vpn { Ok(Vpn {
server: Self::to_cstring(&self.server), server: Self::to_cstring(&self.server),
cookie: Self::to_cstring(&self.cookie), cookie: Self::to_cstring(&self.cookie),
user_agent: Self::to_cstring(&user_agent), user_agent: Self::to_cstring(&user_agent),
@ -154,7 +194,7 @@ impl VpnBuilder {
mtu: self.mtu, mtu: self.mtu,
callback: Default::default(), callback: Default::default(),
} })
} }
fn to_cstring(value: &str) -> CString { fn to_cstring(value: &str) -> CString {

View File

@ -1,24 +0,0 @@
use is_executable::IsExecutable;
use std::path::Path;
const VPNC_SCRIPT_LOCATIONS: [&str; 6] = [
"/usr/local/share/vpnc-scripts/vpnc-script",
"/usr/local/sbin/vpnc-script",
"/usr/share/vpnc-scripts/vpnc-script",
"/usr/sbin/vpnc-script",
"/etc/vpnc/vpnc-script",
"/etc/openconnect/vpnc-script"
];
pub(crate) fn find_default_vpnc_script() -> Option<String> {
for location in VPNC_SCRIPT_LOCATIONS.iter() {
let path = Path::new(location);
if path.is_executable() {
return Some(location.to_string());
}
}
log::warn!("vpnc-script not found");
None
}