diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2017-03-06 19:41:33 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2017-03-07 08:42:02 +0100 |
| commit | ae3458fdd2ee092924ce2f275e622f6a66fab903 (patch) | |
| tree | a6f5a196c0c0b7d3b5b9080ce7d80e9bfb4b2210 /openvpn_ffi/src | |
| parent | f085642d25c2f91095ce01d324a59b5cee23e8b0 (diff) | |
| download | mullvadvpn-ae3458fdd2ee092924ce2f275e622f6a66fab903.tar.xz mullvadvpn-ae3458fdd2ee092924ce2f275e622f6a66fab903.zip | |
Extract OpenVPN FFI stuff to separate crate
Diffstat (limited to 'openvpn_ffi/src')
| -rw-r--r-- | openvpn_ffi/src/lib.rs | 142 | ||||
| -rw-r--r-- | openvpn_ffi/src/parse.rs | 142 | ||||
| -rw-r--r-- | openvpn_ffi/src/structs.rs | 49 |
3 files changed, 333 insertions, 0 deletions
diff --git a/openvpn_ffi/src/lib.rs b/openvpn_ffi/src/lib.rs new file mode 100644 index 0000000000..6deed54e37 --- /dev/null +++ b/openvpn_ffi/src/lib.rs @@ -0,0 +1,142 @@ +//! Constants for OpenVPN. Taken from include/openvpn-plugin.h in the OpenVPN repository: +//! https://github.com/OpenVPN/openvpn/blob/master/include/openvpn-plugin.h.in + +#[macro_use] +extern crate error_chain; + +#[cfg(test)] +#[macro_use] +extern crate assert_matches; + +use std::os::raw::c_int; + +mod structs; +pub use structs::*; + +pub mod parse; + +error_chain!{ + errors { + InvalidEnumVariant(i: c_int) { + description("Integer does not match any enum variant") + display("{} is not a valid OPENVPN_PLUGIN_* constant", i) + } + } +} + + +// Return values. Returned from the plugin to OpenVPN to indicate success or failure. Can also +// Accept (success) or decline (error) an operation, such as incoming client connection attempt. +pub const OPENVPN_PLUGIN_FUNC_SUCCESS: c_int = 0; +pub const OPENVPN_PLUGIN_FUNC_ERROR: c_int = 1; +pub const OPENVPN_PLUGIN_FUNC_DEFERRED: c_int = 2; + + +/// Enum whose variants correspond to the `OPENVPN_PLUGIN_*` event constants. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum OpenVpnPluginEvent { + Up = 0, + Down = 1, + RouteUp = 2, + IpChange = 3, + TlsVerify = 4, + AuthUserPassVerify = 5, + ClientConnect = 6, + ClientDisconnect = 7, + LearnAddress = 8, + ClientConnectV2 = 9, + TlsFinal = 10, + EnablePf = 11, + RoutePredown = 12, + N = 13, +} + +impl OpenVpnPluginEvent { + /// Tries to parse an integer from C into a variant of `OpenVpnPluginEvent`. + pub fn from_int(i: c_int) -> Result<OpenVpnPluginEvent> { + if i >= OpenVpnPluginEvent::Up as c_int && i <= OpenVpnPluginEvent::N as c_int { + Ok(unsafe { ::std::mem::transmute_copy::<c_int, OpenVpnPluginEvent>(&i) }) + } else { + Err(ErrorKind::InvalidEnumVariant(i).into()) + } + } +} + +/// Translates a collection of `OpenVpnPluginEvent` instances into a bitmask in the format OpenVPN +/// expects it. +pub fn events_to_bitmask(events: &[OpenVpnPluginEvent]) -> c_int { + let mut bitmask: c_int = 0; + for event in events { + bitmask |= 1 << (*event as i32); + } + bitmask +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_int_first() { + let result = OpenVpnPluginEvent::from_int(0); + assert_matches!(result, Ok(OpenVpnPluginEvent::Up)); + } + + #[test] + fn from_int_last() { + let result = OpenVpnPluginEvent::from_int(13); + assert_matches!(result, Ok(OpenVpnPluginEvent::N)); + } + + #[test] + fn from_int_all_valid() { + for i in 0..13 { + if OpenVpnPluginEvent::from_int(i).is_err() { + panic!("{} not covered", i); + } + } + } + + #[test] + fn from_int_negative() { + let result = OpenVpnPluginEvent::from_int(-5); + assert_matches!(result, Err(Error(ErrorKind::InvalidEnumVariant(-5), _))); + } + + #[test] + fn from_int_invalid() { + let result = OpenVpnPluginEvent::from_int(14); + assert_matches!(result, Err(Error(ErrorKind::InvalidEnumVariant(14), _))); + } + + #[test] + fn event_enum_to_str() { + let result = format!("{:?}", OpenVpnPluginEvent::Up); + assert_eq!("Up", result); + } + + #[test] + fn events_to_bitmask_no_events() { + let result = events_to_bitmask(&[]); + assert_eq!(0, result); + } + + #[test] + fn events_to_bitmask_one_event() { + let result = events_to_bitmask(&[OpenVpnPluginEvent::Up]); + assert_eq!(0b1, result); + } + + #[test] + fn events_to_bitmask_another_event() { + let result = events_to_bitmask(&[OpenVpnPluginEvent::RouteUp]); + assert_eq!(0b100, result); + } + + #[test] + fn events_to_bitmask_many_events() { + let result = events_to_bitmask(&[OpenVpnPluginEvent::RouteUp, OpenVpnPluginEvent::N]); + assert_eq!((1 << 13) + (1 << 2), result); + } +} diff --git a/openvpn_ffi/src/parse.rs b/openvpn_ffi/src/parse.rs new file mode 100644 index 0000000000..096e3ada55 --- /dev/null +++ b/openvpn_ffi/src/parse.rs @@ -0,0 +1,142 @@ +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::raw::c_char; + +error_chain!{ + errors { + Null { + description("Input is null pointer") + } + NoEqual(s: String) { + description("No equal sign in string") + display("No equal sign in \"{}\"", s) + } + } + foreign_links { + InvalidContent(::std::str::Utf8Error); + } +} + + +/// Parses a null-terminated C string array into a Vec<String> for safe usage. +/// +/// Returns an Err if given a null pointer or if a string is not valid utf-8. +/// +/// # Segfaults +/// +/// Can cause the program to crash if the pointer array starting at `ptr` is not correctly null +/// terminated. Likewise, if any string pointed to is not properly null-terminated it may crash. +pub unsafe fn string_array(mut ptr: *const *const c_char) -> Result<Vec<String>> { + if ptr.is_null() { + Err(ErrorKind::Null.into()) + } else { + let mut strings = Vec::new(); + while !(*ptr).is_null() { + let cstr = CStr::from_ptr(*ptr); + strings.push(cstr.to_str()?.to_owned()); + ptr = ptr.offset(1); + } + Ok(strings) + } +} + + +/// Parses a null-terminated array of C strings with "=" delimiters into a key-value map. +/// +/// The input environment has to contain null-terminated strings containing at least +/// one equal sign ("="). Every string is split at the first equal sign and added to the map with +/// the first part being the key and the second the value. +/// +/// If multiple entries have the same key, the last one will be in the result map. +/// +/// # Segfaults +/// +/// Uses `string_array` internally and will segfault for the same reasons as that function. +pub unsafe fn env(envptr: *const *const c_char) -> Result<HashMap<String, String>> { + let mut map = HashMap::new(); + for string in string_array(envptr)? { + let mut iter = string.splitn(2, '='); + let key = iter.next().unwrap(); + let value = iter.next().ok_or_else(|| Error::from(ErrorKind::NoEqual(string.clone())))?; + map.insert(key.to_owned(), value.to_owned()); + } + Ok(map) +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::os::raw::c_char; + use std::ptr; + + #[test] + fn string_array_null() { + let result = unsafe { string_array(ptr::null()) }; + assert_matches!(result, Err(Error(ErrorKind::Null, _))); + } + + #[test] + fn string_array_empty() { + let ptr_arr = [ptr::null()]; + let result = unsafe { string_array(&ptr_arr as *const *const c_char).unwrap() }; + assert!(result.is_empty()); + } + + #[test] + fn string_array_no_space_trim() { + let test_str = " foobar \0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { string_array(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!([" foobar "], &result[..]); + } + + #[test] + fn string_array_two_strings() { + let test_str1 = "foobar\0"; + let test_str2 = "barbaz\0"; + let ptr_arr = [test_str1 as *const _ as *const c_char, + test_str2 as *const _ as *const c_char, + ptr::null()]; + let result = unsafe { string_array(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(["foobar", "barbaz"], &result[..]); + } + + #[test] + fn env_one_value() { + let test_str = "var_a=value_b\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { env(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(1, result.len()); + assert_eq!(Some("value_b"), result.get("var_a").map(|s| &s[..])); + } + + #[test] + fn env_no_equal() { + let test_str = "foobar\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { env(&ptr_arr as *const *const c_char) }; + assert_matches!(result, Err(Error(ErrorKind::NoEqual(_), _))); + } + + #[test] + fn env_double_equal() { + let test_str = "foo=bar=baz\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let env = unsafe { env(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(1, env.len()); + assert_eq!(Some("bar=baz"), env.get("foo").map(|s| &s[..])); + } + + #[test] + fn env_two_same_key() { + let test_str1 = "foo=123\0"; + let test_str2 = "foo=abc\0"; + let ptr_arr = [test_str1 as *const _ as *const c_char, + test_str2 as *const _ as *const c_char, + ptr::null()]; + let env = unsafe { env(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(1, env.len()); + assert_eq!(Some("abc"), env.get("foo").map(|s| &s[..])); + } +} diff --git a/openvpn_ffi/src/structs.rs b/openvpn_ffi/src/structs.rs new file mode 100644 index 0000000000..b80f7ae2a2 --- /dev/null +++ b/openvpn_ffi/src/structs.rs @@ -0,0 +1,49 @@ +use std::os::raw::{c_char, c_int, c_uint, c_void}; + +/// Struct sent to `openvpn_plugin_open_v3` containing input values. +#[repr(C)] +pub struct openvpn_plugin_args_open_in { + type_mask: c_int, + pub argv: *const *const c_char, + envp: *const *const c_char, + callbacks: *const c_void, + ssl_api: ovpnSSLAPI, + ovpn_version: *const c_char, + ovpn_version_major: c_uint, + ovpn_version_minor: c_uint, + ovpn_version_patch: *const c_char, +} + +#[allow(dead_code)] +#[repr(C)] +enum ovpnSSLAPI { + None, + OpenSsl, + MbedTls, +} + +/// Struct used for returning values from `openvpn_plugin_open_v3` to OpenVPN. +#[repr(C)] +pub struct openvpn_plugin_args_open_return { + pub type_mask: c_int, + pub handle: *const c_void, + return_list: *const c_void, +} + +/// Struct sent to `openvpn_plugin_func_v3` containing input values. +#[repr(C)] +pub struct openvpn_plugin_args_func_in { + pub event_type: c_int, + argv: *const *const c_char, + pub envp: *const *const c_char, + pub handle: *const c_void, + per_client_context: *const c_void, + current_cert_depth: c_int, + current_cert: *const c_void, +} + +/// Struct used for returning values from `openvpn_plugin_func_v3` to OpenVPN. +#[repr(C)] +pub struct openvpn_plugin_args_func_return { + return_list: *const c_void, +} |
