Skip to content

Commit

Permalink
Merge pull request #42 from jamesmcm/openconnect
Browse files Browse the repository at this point in the history
Add preliminary Cisco OpenConnect support.
  • Loading branch information
jamesmcm authored Oct 31, 2020
2 parents e3ed55a + 191a594 commit ef9653b
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "vopono"
description = "Launch applications via VPN tunnels using temporary network namespaces"
version = "0.5.1"
version = "0.5.2"
authors = ["James McMurray <[email protected]>"]
edition = "2018"
license = "GPL-3.0-or-later"
Expand Down
33 changes: 27 additions & 6 deletions src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"),
}?;
if !cdir.exists() || cdir.read_dir()?.next().is_none() {
info!(
Expand Down Expand Up @@ -96,10 +97,16 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"),
}?;
get_config_from_alias(&cdir, &server_name)?
Some(get_config_from_alias(&cdir, &server_name)?)
} else {
command.custom_config.expect("No custom config provided")
// Config file required for non OpenConnect custom providers
if protocol != Protocol::OpenConnect {
Some(command.custom_config.expect("No custom config provided"))
} else {
None
}
};

// Better to check for lockfile exists?
Expand Down Expand Up @@ -146,15 +153,17 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
ns.dns_config(&dns)?;

// Check if using Shadowsocks
if let Some((ss_host, ss_lport)) = uses_shadowsocks(&config_file)? {
if let Some((ss_host, ss_lport)) =
uses_shadowsocks(config_file.as_ref().expect("No config file provided"))?
{
if provider == VpnProvider::Custom {
warn!("Custom provider specifies socks-proxy, if this is local you must run it yourself (e.g. shadowsocks)");
} else {
let dyn_ss_provider = provider.get_dyn_shadowsocks_provider()?;
let password = dyn_ss_provider.password();
let encrypt_method = dyn_ss_provider.encrypt_method();
ns.run_shadowsocks(
&config_file,
config_file.as_ref().expect("No config file provided"),
ss_host,
ss_lport,
&password,
Expand All @@ -164,7 +173,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
}

ns.run_openvpn(
config_file,
config_file.expect("No config file provided"),
auth_file,
&dns,
!command.no_killswitch,
Expand All @@ -188,13 +197,25 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
}
Protocol::Wireguard => {
ns.run_wireguard(
config_file,
config_file.expect("No config file provided"),
!command.no_killswitch,
command.forward_ports.as_ref(),
firewall,
command.disable_ipv6,
)?;
}
Protocol::OpenConnect => {
let dns = command
.dns
.unwrap_or_else(|| vec![IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))]);
ns.dns_config(&dns)?;
ns.run_openconnect(
config_file,
command.forward_ports.as_ref(),
firewall,
&server_name,
)?;
}
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ pub fn print_applications() -> anyhow::Result<()> {
}
}
// Avoid triggering Drop for these namespaces
let namespaces = Box::new(namespaces);
Box::leak(namespaces);
std::mem::forget(namespaces);
Ok(())
}

Expand Down Expand Up @@ -82,8 +81,7 @@ pub fn print_namespaces() -> anyhow::Result<()> {
}
}
// Avoid triggering Drop for these namespaces
let namespaces = Box::new(namespaces);
Box::leak(namespaces);
std::mem::forget(namespaces);
Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions src/list_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn print_configs(cmd: ServersCommand) -> anyhow::Result<()> {
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::OpenConnect => bail!("Config listing not implemented for OpenConnect"),
}?;
if !cdir.exists() || cdir.read_dir()?.next().is_none() {
bail!(
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod list;
mod list_configs;
mod netns;
mod network_interface;
mod openconnect;
mod openvpn;
mod providers;
mod shadowsocks;
Expand Down
46 changes: 26 additions & 20 deletions src/netns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::dns_config::DnsConfig;
use super::firewall::Firewall;
use super::host_masquerade::HostMasquerade;
use super::network_interface::NetworkInterface;
use super::openconnect::OpenConnect;
use super::openvpn::OpenVpn;
use super::shadowsocks::Shadowsocks;
use super::util::{config_dir, set_config_permissions, sudo_command};
Expand Down Expand Up @@ -30,6 +31,7 @@ pub struct NetworkNamespace {
pub host_masquerade: Option<HostMasquerade>,
pub shadowsocks: Option<Shadowsocks>,
pub veth_pair_ips: Option<VethPairIPs>,
pub openconnect: Option<OpenConnect>,
pub provider: VpnProvider,
pub protocol: Protocol,
pub firewall: Firewall,
Expand Down Expand Up @@ -78,6 +80,7 @@ impl NetworkNamespace {
host_masquerade: None,
shadowsocks: None,
veth_pair_ips: None,
openconnect: None,
provider,
protocol,
firewall,
Expand Down Expand Up @@ -221,6 +224,23 @@ impl NetworkNamespace {
Ok(())
}

pub fn run_openconnect(
&mut self,
config_file: Option<PathBuf>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
server: &str,
) -> anyhow::Result<()> {
self.openconnect = Some(OpenConnect::run(
&self,
config_file,
forward_ports,
firewall,
server,
)?);
Ok(())
}

pub fn run_shadowsocks(
&mut self,
config_file: &PathBuf,
Expand Down Expand Up @@ -350,26 +370,12 @@ impl Drop for NetworkNamespace {
.unwrap_or_else(|_| panic!("Failed to delete network namespace: {}", &self.name));
} else {
debug!("Skipping destructors since other vopono instance using this namespace!");
// TODO: Test std::mem::forget(self) here
let openvpn = self.openvpn.take();
let openvpn = Box::new(openvpn);
Box::leak(openvpn);

let veth_pair = self.veth_pair.take();
let veth_pair = Box::new(veth_pair);
Box::leak(veth_pair);

let dns_config = self.dns_config.take();
let dns_config = Box::new(dns_config);
Box::leak(dns_config);

let wireguard = self.wireguard.take();
let wireguard = Box::new(wireguard);
Box::leak(wireguard);

let host_masquerade = self.host_masquerade.take();
let host_masquerade = Box::new(host_masquerade);
Box::leak(host_masquerade);
std::mem::forget(self.openvpn.take());
std::mem::forget(self.veth_pair.take());
std::mem::forget(self.dns_config.take());
std::mem::forget(self.wireguard.take());
std::mem::forget(self.host_masquerade.take());
std::mem::forget(self.openconnect.take());
}
}
}
Expand Down
91 changes: 91 additions & 0 deletions src/openconnect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use super::firewall::Firewall;
use super::netns::NetworkNamespace;
use anyhow::{anyhow, Context};
use dialoguer::{Input, Password};
use log::{debug, error, info};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Serialize, Deserialize)]
pub struct OpenConnect {
pid: u32,
}

impl OpenConnect {
#[allow(clippy::too_many_arguments)]
pub fn run(
netns: &NetworkNamespace,
config_file: Option<PathBuf>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
server: &str,
) -> anyhow::Result<Self> {
if let Err(x) = which::which("openconnect") {
error!("OpenConnect not found. Is OpenConnect installed and on PATH?");
return Err(anyhow!(
"OpenConnect not found. Is OpenConnect installed and on PATH?: {:?}",
x
));
}

let handle;

let creds = {
if let Some(config_file) = config_file {
let config_file_path = config_file.canonicalize().context("Invalid path given")?;
get_creds_from_file(&config_file_path)
} else {
request_creds()
}
}?;

info!("Launching OpenConnect...");
// TODO: Auth
let user_arg = format!("--user={}", creds.0);
let command_vec = (&["openconnect", &user_arg, "--passwd-on-stdin", server]).to_vec();

handle = netns
.exec_no_block(&command_vec, None, false, None)
.context("Failed to launch OpenConnect - is openconnect installed?")?;
let id = handle.id();

// Allow input to and output from forwarded ports
if let Some(forwards) = forward_ports {
super::util::open_ports(&netns, forwards.as_slice(), firewall)?;
}

Ok(Self { pid: id })
}
}

fn get_creds_from_file(auth_file: &PathBuf) -> anyhow::Result<(String, String)> {
let s = std::fs::read_to_string(auth_file)?;
let mut iter = s.split('\n');
let user = iter.next().expect("No username in auth file");
let pass = iter.next().expect("No password in auth file");
Ok((user.to_string(), pass.to_string()))
}

fn request_creds() -> anyhow::Result<(String, String)> {
let username = Input::<String>::new()
.with_prompt("OpenConnect username")
.interact()?;
let username = username.trim();
let password = Password::new()
.with_prompt("OpenConnect password")
.interact()?;
let password = password.trim();
Ok((username.to_string(), password.to_string()))
}

impl Drop for OpenConnect {
fn drop(&mut self) {
match nix::sys::signal::kill(
nix::unistd::Pid::from_raw(self.pid as i32),
nix::sys::signal::Signal::SIGKILL,
) {
Ok(_) => debug!("Killed OpenConnect (pid: {})", self.pid),
Err(e) => error!("Failed to kill OpenConnect (pid: {}): {:?}", self.pid, e),
}
}
}
5 changes: 4 additions & 1 deletion src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::util::set_config_permissions;
use super::vpn::Protocol;
use anyhow::bail;
use dialoguer::MultiSelect;
use log::info;
use log::{error, info};
use std::str::FromStr;

pub fn sync_menu() -> anyhow::Result<()> {
Expand Down Expand Up @@ -46,6 +46,9 @@ pub fn synch(provider: VpnProvider, protocol: Option<Protocol>) -> anyhow::Resul
let provider = provider.get_dyn_wireguard_provider()?;
provider.create_wireguard_config()?;
}
Some(Protocol::OpenConnect) => {
error!("vopono sync not supported for OpenConnect protocol");
}
// TODO: Fix this asking for same credentials twice
None => {
if let Ok(p) = provider.get_dyn_wireguard_provider() {
Expand Down
4 changes: 2 additions & 2 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ pub fn clean_dead_namespaces() -> anyhow::Result<()> {
.collect::<Result<(), _>>()?;

// TODO - deserialize to struct without Drop instead
let lock_namespaces = Box::new(lock_namespaces);
Box::leak(lock_namespaces);
std::mem::forget(lock_namespaces);
Ok(())
}

Expand Down Expand Up @@ -326,6 +325,7 @@ pub fn get_config_file_protocol(config_file: &PathBuf) -> Protocol {
if content.contains(&"[Interface]") {
Protocol::Wireguard
} else {
// TODO: Don't always assume OpenVPN
Protocol::OpenVpn
}
}
Expand Down
1 change: 1 addition & 0 deletions src/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ arg_enum! {
pub enum Protocol {
OpenVpn,
Wireguard,
OpenConnect,
}
}

Expand Down

0 comments on commit ef9653b

Please sign in to comment.