diff --git a/.vscode/settings.json b/.vscode/settings.json index b4ce30f..ab13528 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "bincode", "chacha", "clientos", + "cstring", "datetime", "disconnectable", "distro", diff --git a/apps/gpclient/src/connect.rs b/apps/gpclient/src/connect.rs index 126f8cc..5ed0792 100644 --- a/apps/gpclient/src/connect.rs +++ b/apps/gpclient/src/connect.rs @@ -7,7 +7,10 @@ use gpapi::{ gateway::gateway_login, gp_params::{ClientOs, GpParams}, portal::{prelogin, retrieve_config, PortalError, Prelogin}, - process::auth_launcher::SamlAuthLauncher, + process::{ + auth_launcher::SamlAuthLauncher, + users::{get_non_root_user, get_user_by_name}, + }, utils::shutdown_signal, GP_USER_AGENT, }; @@ -27,6 +30,13 @@ pub(crate) struct ConnectArgs { user: Option, #[arg(long, short, help = "The VPNC script to use")] script: Option, + + #[arg(long, help = "Same as the '--csd-user' option in the openconnect command")] + csd_user: Option, + + #[arg(long, help = "Same as the '--csd-wrapper' option in the openconnect command")] + csd_wrapper: Option, + #[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")] user_agent: String, #[arg(long, default_value = "Linux")] @@ -133,7 +143,7 @@ impl<'a> ConnectHandler<'a> { gp_params.set_is_gateway(true); let prelogin = prelogin(gateway, &gp_params).await?; - let cred = self.obtain_credential(&prelogin, &gateway).await?; + let cred = self.obtain_credential(&prelogin, gateway).await?; let cookie = gateway_login(gateway, &cred, &gp_params).await?; @@ -141,9 +151,13 @@ impl<'a> ConnectHandler<'a> { } async fn connect_gateway(&self, gateway: &str, cookie: &str) -> anyhow::Result<()> { + let csd_uid = get_csd_uid(&self.args.csd_user)?; + let vpn = Vpn::builder(gateway, cookie) .user_agent(self.args.user_agent.clone()) .script(self.args.script.clone()) + .csd_uid(csd_uid) + .csd_wrapper(self.args.csd_wrapper.clone()) .build(); let vpn = Arc::new(vpn); @@ -211,3 +225,11 @@ fn write_pid_file() { fs::write(GP_CLIENT_LOCK_FILE, pid.to_string()).unwrap(); info!("Wrote PID {} to {}", pid, GP_CLIENT_LOCK_FILE); } + +fn get_csd_uid(csd_user: &Option) -> anyhow::Result { + if let Some(csd_user) = csd_user { + get_user_by_name(csd_user).map(|user| user.uid()) + } else { + get_non_root_user().map_or_else(|_| Ok(0), |user| Ok(user.uid())) + } +} diff --git a/crates/gpapi/src/process/command_traits.rs b/crates/gpapi/src/process/command_traits.rs index e20f3cc..edda8d7 100644 --- a/crates/gpapi/src/process/command_traits.rs +++ b/crates/gpapi/src/process/command_traits.rs @@ -1,7 +1,8 @@ -use anyhow::bail; -use std::{env, ffi::OsStr}; +use std::ffi::OsStr; use tokio::process::Command; -use uzers::{os::unix::UserExt, User}; +use uzers::os::unix::UserExt; + +use super::users::get_non_root_user; pub trait CommandExt { fn new_pkexec>(program: S) -> Command; @@ -34,29 +35,3 @@ impl CommandExt for Command { Ok(self) } } - -fn get_non_root_user() -> anyhow::Result { - let current_user = whoami::username(); - - let user = if current_user == "root" { - get_real_user()? - } else { - uzers::get_user_by_name(¤t_user).ok_or_else(|| anyhow::anyhow!("User ({}) not found", current_user))? - }; - - if user.uid() == 0 { - bail!("Non-root user not found") - } - - Ok(user) -} - -fn get_real_user() -> anyhow::Result { - // Read the UID from SUDO_UID or PKEXEC_UID environment variable if available. - let uid = match env::var("SUDO_UID") { - Ok(uid) => uid.parse::()?, - _ => env::var("PKEXEC_UID")?.parse::()?, - }; - - uzers::get_user_by_uid(uid).ok_or_else(|| anyhow::anyhow!("User not found")) -} diff --git a/crates/gpapi/src/process/mod.rs b/crates/gpapi/src/process/mod.rs index b0842e4..cb9db31 100644 --- a/crates/gpapi/src/process/mod.rs +++ b/crates/gpapi/src/process/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod command_traits; +pub mod users; pub mod auth_launcher; #[cfg(feature = "browser-auth")] pub mod browser_authenticator; diff --git a/crates/gpapi/src/process/users.rs b/crates/gpapi/src/process/users.rs new file mode 100644 index 0000000..0fc51ef --- /dev/null +++ b/crates/gpapi/src/process/users.rs @@ -0,0 +1,34 @@ +use std::env; + +use anyhow::bail; +use uzers::User; + +pub fn get_user_by_name(username: &str) -> anyhow::Result { + uzers::get_user_by_name(username).ok_or_else(|| anyhow::anyhow!("User ({}) not found", username)) +} + +pub fn get_non_root_user() -> anyhow::Result { + let current_user = whoami::username(); + + let user = if current_user == "root" { + get_real_user()? + } else { + get_user_by_name(¤t_user)? + }; + + if user.uid() == 0 { + bail!("Non-root user not found") + } + + Ok(user) +} + +fn get_real_user() -> anyhow::Result { + // Read the UID from SUDO_UID or PKEXEC_UID environment variable if available. + let uid = match env::var("SUDO_UID") { + Ok(uid) => uid.parse::()?, + _ => env::var("PKEXEC_UID")?.parse::()?, + }; + + uzers::get_user_by_uid(uid).ok_or_else(|| anyhow::anyhow!("User not found")) +} diff --git a/crates/openconnect/src/ffi/mod.rs b/crates/openconnect/src/ffi/mod.rs index c312eda..0d5d21e 100644 --- a/crates/openconnect/src/ffi/mod.rs +++ b/crates/openconnect/src/ffi/mod.rs @@ -15,6 +15,9 @@ pub(crate) struct ConnectOptions { pub os: *const c_char, pub certificate: *const c_char, pub servercert: *const c_char, + + pub csd_uid: u32, + pub csd_wrapper: *const c_char, } #[link(name = "vpn")] diff --git a/crates/openconnect/src/ffi/vpn.c b/crates/openconnect/src/ffi/vpn.c index e3d00c3..265a4e2 100644 --- a/crates/openconnect/src/ffi/vpn.c +++ b/crates/openconnect/src/ffi/vpn.c @@ -61,6 +61,8 @@ int vpn_connect(const vpn_options *options, vpn_connected_callback callback) INFO("User agent: %s", options->user_agent); INFO("VPNC script: %s", options->script); INFO("OS: %s", options->os); + INFO("CSD_USER: %d", options->csd_uid); + INFO("CSD_WRAPPER: %s", options->csd_wrapper); vpninfo = openconnect_vpninfo_new(options->user_agent, validate_peer_cert, NULL, NULL, print_progress, NULL); @@ -91,6 +93,10 @@ int vpn_connect(const vpn_options *options, vpn_connected_callback callback) openconnect_set_system_trust(vpninfo, 0); } + if (options->csd_wrapper) { + openconnect_setup_csd(vpninfo, options->csd_uid, 1, options->csd_wrapper); + } + g_cmd_pipe_fd = openconnect_setup_cmd_pipe(vpninfo); if (g_cmd_pipe_fd < 0) { diff --git a/crates/openconnect/src/ffi/vpn.h b/crates/openconnect/src/ffi/vpn.h index 91a31d4..e304ab5 100644 --- a/crates/openconnect/src/ffi/vpn.h +++ b/crates/openconnect/src/ffi/vpn.h @@ -16,6 +16,9 @@ typedef struct vpn_options const char *os; const char *certificate; const char *servercert; + + const uid_t csd_uid; + const char *csd_wrapper; } vpn_options; int vpn_connect(const vpn_options *options, vpn_connected_callback callback); diff --git a/crates/openconnect/src/vpn.rs b/crates/openconnect/src/vpn.rs index b260804..f41d1e0 100644 --- a/crates/openconnect/src/vpn.rs +++ b/crates/openconnect/src/vpn.rs @@ -18,6 +18,9 @@ pub struct Vpn { certificate: Option, servercert: Option, + csd_uid: u32, + csd_wrapper: Option, + callback: OnConnectedCallback, } @@ -56,6 +59,9 @@ impl Vpn { os: self.os.as_ptr(), certificate: Self::option_to_ptr(&self.certificate), servercert: Self::option_to_ptr(&self.servercert), + + csd_uid: self.csd_uid, + csd_wrapper: Self::option_to_ptr(&self.csd_wrapper), } } @@ -73,6 +79,9 @@ pub struct VpnBuilder { user_agent: Option, script: Option, os: Option, + + csd_uid: u32, + csd_wrapper: Option, } impl VpnBuilder { @@ -83,6 +92,8 @@ impl VpnBuilder { user_agent: None, script: None, os: None, + csd_uid: 0, + csd_wrapper: None, } } @@ -101,6 +112,16 @@ impl VpnBuilder { self } + pub fn csd_uid(mut self, csd_uid: u32) -> Self { + self.csd_uid = csd_uid; + self + } + + pub fn csd_wrapper>>(mut self, csd_wrapper: T) -> Self { + self.csd_wrapper = csd_wrapper.into(); + self + } + pub fn build(self) -> Vpn { let user_agent = self.user_agent.unwrap_or_default(); let script = self.script.or_else(find_default_vpnc_script).unwrap_or_default(); @@ -114,6 +135,10 @@ impl VpnBuilder { os: Self::to_cstring(&os), certificate: None, servercert: None, + + csd_uid: self.csd_uid, + csd_wrapper: self.csd_wrapper.as_deref().map(Self::to_cstring), + callback: Default::default(), } }