Skip to content

Commit

Permalink
merge from master
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmcm committed Jan 20, 2024
2 parents 6919ca8 + 70a2aff commit 9820ef8
Show file tree
Hide file tree
Showing 10 changed files with 410 additions and 38 deletions.
11 changes: 8 additions & 3 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,14 @@ pub struct ExecCommand {
#[clap(long = "allow-host-access")]
pub allow_host_access: bool,

/// Enable port forwarding for ProtonVPN connections
#[clap(long = "protonvpn-port-forwarding")]
pub protonvpn_port_forwarding: bool,
/// Enable port forwarding for if supported
#[clap(long = "port-forwarding")]
pub port_forwarding: bool,

/// Path or alias to executable script or binary to be called with the port as an argumnet
/// when the port forwarding is refreshed (PIA only)
#[clap(long = "port-forwarding-callback")]
pub port_forwarding_callback: Option<String>,

/// Only create network namespace (does not run application)
#[clap(long = "create-netns-only")]
Expand Down
68 changes: 48 additions & 20 deletions src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use vopono_core::config::vpn::{verify_auth, Protocol};
use vopono_core::network::application_wrapper::ApplicationWrapper;
use vopono_core::network::firewall::Firewall;
use vopono_core::network::natpmpc::Natpmpc;
use vopono_core::network::piapf::Piapf;
use vopono_core::network::Forwarder;
use vopono_core::network::netns::NetworkNamespace;
use vopono_core::network::network_interface::{get_active_interfaces, NetworkInterface};
use vopono_core::network::shadowsocks::uses_shadowsocks;
Expand Down Expand Up @@ -139,15 +141,15 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
command.working_directory
};

// Port forwarding for ProtonVPN
let protonvpn_port_forwarding = if !command.protonvpn_port_forwarding {
// Port forwarding
let port_forwarding = if !command.port_forwarding {
vopono_config_settings
.get("protonvpn-port-forwarding")
.get("port-forwarding")
.map_err(|_e| anyhow!("Failed to read config file"))
.ok()
.unwrap_or(false)
} else {
command.protonvpn_port_forwarding
command.port_forwarding
};

// Create netns only
Expand Down Expand Up @@ -432,7 +434,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
}

ns.run_openvpn(
config_file.expect("No config file provided"),
config_file.clone().expect("No config file provided"),
auth_file,
&dns,
!command.no_killswitch,
Expand Down Expand Up @@ -467,7 +469,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
}
Protocol::Wireguard => {
ns.run_wireguard(
config_file.expect("No config file provided"),
config_file.clone().expect("No config file provided"),
!command.no_killswitch,
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
Expand All @@ -482,7 +484,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
// TODO: DNS suffixes?
ns.dns_config(&dns, &[], command.hosts_entries.as_ref())?;
ns.run_openconnect(
config_file.expect("No OpenConnect config file provided"),
config_file.clone().expect("No OpenConnect config file provided"),
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
firewall,
Expand All @@ -493,7 +495,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
Protocol::OpenFortiVpn => {
// TODO: DNS handled by OpenFortiVpn directly?
ns.run_openfortivpn(
config_file.expect("No OpenFortiVPN config file provided"),
config_file.clone().expect("No OpenFortiVPN config file provided"),
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
command.hosts_entries.as_ref(),
Expand Down Expand Up @@ -550,19 +552,45 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>

let ns = ns.write_lockfile(&command.application)?;

let natpmpc = if protonvpn_port_forwarding {
vopono_core::util::open_hosts(
&ns,
vec![vopono_core::network::natpmpc::PROTONVPN_GATEWAY],
firewall,
)?;
Some(Natpmpc::new(&ns)?)
let forwarder: Option<Box<dyn Forwarder>> = if port_forwarding {

let callback = command
.port_forwarding_callback
.or_else(|| {
vopono_config_settings
.get("port_forwarding_callback")
.map_err(|_e| anyhow!("Failed to read config file"))
.ok()
});
match provider {
VpnProvider::PrivateInternetAccess => {
let conf_path = config_file
.expect("No PIA config file provided");
let conf_name = conf_path
.file_name().unwrap()
.to_str().expect("No filename for PIA config file")
.to_string();
Some(Box::new(Piapf::new(&ns, &conf_name, &protocol, callback.as_ref())?))
},
VpnProvider::ProtonVPN => {
vopono_core::util::open_hosts(
&ns,
vec![vopono_core::network::natpmpc::PROTONVPN_GATEWAY],
firewall,
)?;
Some(Box::new(Natpmpc::new(&ns)?))
},
_ => {
anyhow::bail!("Port forwarding not supported for the selected provider");
}
}
} else {
None
};

if let Some(pmpc) = natpmpc.as_ref() {
vopono_core::util::open_ports(&ns, &[pmpc.local_port], firewall)?;
// TODO: The forwarder should probably be able to do this (pass firewall?)
if let Some(fwd) = forwarder.as_ref() {
vopono_core::util::open_ports(&ns, &[fwd.forwarded_port()], firewall)?;
}

// Launch TCP proxy server on other threads if forwarding ports
Expand Down Expand Up @@ -592,7 +620,7 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
user,
group,
working_directory.map(PathBuf::from),
natpmpc,
forwarder,
)?;

let pid = application.handle.id();
Expand All @@ -601,8 +629,8 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
&command.application, &ns.name, pid
);

if let Some(pmpc) = application.protonvpn_port_forwarding.as_ref() {
info!("ProtonVPN Port Forwarding on port {}", pmpc.local_port)
if let Some(fwd) = application.port_forwarding.as_ref() {
info!("Port Forwarding on port {}", fwd.forwarded_port())
}
let output = application.wait_with_output()?;
io::stdout().write_all(output.stdout.as_slice())?;
Expand Down
1 change: 1 addition & 0 deletions vopono_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ signal-hook = "0.3"
sha2 = "0.10"
tiny_http = "0.12"
chrono = "0.4"
json = "0.12"
25 changes: 22 additions & 3 deletions vopono_core/src/config/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod ivpn;
mod mozilla;
mod mullvad;
mod nordvpn;
mod pia;
pub mod pia;
mod protonvpn;
mod ui;
mod warp;
Expand All @@ -14,8 +14,12 @@ use crate::config::vpn::Protocol;
use crate::util::vopono_dir;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{net::IpAddr, path::Path};
use std::{
net::IpAddr,
path::{Path, PathBuf},
fs::File,
io::{BufReader, BufRead},
};
use strum_macros::{Display, EnumIter};
// TODO: Consider removing this re-export
pub use ui::*;
Expand Down Expand Up @@ -137,6 +141,21 @@ pub trait OpenVpnProvider: Provider {
fn prompt_for_auth(&self, uiclient: &dyn UiClient) -> anyhow::Result<(String, String)>;
fn auth_file_path(&self) -> anyhow::Result<Option<PathBuf>>;


fn load_openvpn_auth(&self) -> anyhow::Result<(String, String)> {
let auth_file = self.auth_file_path()?;
if let Some(auth_file) = auth_file {
let mut reader = BufReader::new(File::open(auth_file)?);
let mut user = String::new();
reader.read_line(&mut user)?;
let mut pass = String::new();
reader.read_line(&mut pass)?;
Ok((user.trim().to_string(), pass.trim().to_string()))
} else {
Err(anyhow!("Auth file required to load credentials!"))
}
}

fn openvpn_dir(&self) -> anyhow::Result<PathBuf> {
Ok(self.provider_dir()?.join("openvpn"))
}
Expand Down
60 changes: 60 additions & 0 deletions vopono_core/src/config/providers/pia/openvpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,38 @@ use std::path::PathBuf;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use zip::ZipArchive;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use log::info;
use regex::Regex;
use anyhow::Context;

#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub hostname_lookup: HashMap<String, String>,
}

impl PrivateInternetAccess {

fn openvpn_config_file_path(&self) -> anyhow::Result<PathBuf> {
Ok(self.openvpn_dir()?.join("config.txt"))
}

//This only works if openvpn was sync'd
pub fn hostname_for_openvpn_conf(&self, config_file: &String) -> anyhow::Result<String> {
let pia_config_file = File::open(self.openvpn_config_file_path()?)?;
let pia_config: Config = serde_json::from_reader(pia_config_file)?;

let hostname = pia_config
.hostname_lookup
.get(config_file)
.with_context(|| format!("Could not find matching hostname for openvpn conf {config_file}"))?;

Ok(hostname.to_string())
}

}

impl OpenVpnProvider for PrivateInternetAccess {
fn provider_dns(&self) -> Option<Vec<IpAddr>> {
Expand Down Expand Up @@ -40,6 +72,11 @@ impl OpenVpnProvider for PrivateInternetAccess {
let country_map = crate::util::country_map::country_to_code_map();
create_dir_all(&openvpn_dir)?;
delete_all_files_in_dir(&openvpn_dir)?;

let mut config = Config {
hostname_lookup: HashMap::new(),
};

for i in 0..zip.len() {
// For each file, detect if ovpn, crl or crt
// Modify auth line for config
Expand Down Expand Up @@ -74,6 +111,20 @@ impl OpenVpnProvider for PrivateInternetAccess {
} else {
file.name().to_string()
};

let re = Regex::new(r"\n *remote +([^ ]+) +\d+ *\n").expect("Failed to compile hostname regex");
if let Some(capture) = re.captures(&String::from_utf8_lossy(&file_contents)) {
let hostname = capture
.get(1)
.expect("No matching hostname group in openvpn config")
.as_str()
.to_string();

info!("Associating {filename} with hostname {hostname}");
config.hostname_lookup.insert(filename.clone(), hostname);
} else {
info!("Configuration {filename} did not have a parseable hostname - port forwarding will not work!");
}

debug!("Reading file: {}", file.name());
let mut outfile =
Expand All @@ -88,8 +139,17 @@ impl OpenVpnProvider for PrivateInternetAccess {
let mut outfile = File::create(auth_file)?;
write!(outfile, "{user}\n{pass}")?;
}

// Write PrivateInternetAccess openvpn config file
let pia_config_file = File::create(self.openvpn_config_file_path()?)?;
serde_json::to_writer(pia_config_file, &config)?;

// Write PIA certificate
self.write_pia_cert()?;

Ok(())
}

}

#[derive(EnumIter, PartialEq)]
Expand Down
Loading

0 comments on commit 9820ef8

Please sign in to comment.