summaryrefslogtreecommitdiffhomepage
path: root/openvpn_ffi/src
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2017-03-06 19:41:33 +0100
committerLinus Färnstrand <linus@mullvad.net>2017-03-07 08:42:02 +0100
commitae3458fdd2ee092924ce2f275e622f6a66fab903 (patch)
treea6f5a196c0c0b7d3b5b9080ce7d80e9bfb4b2210 /openvpn_ffi/src
parentf085642d25c2f91095ce01d324a59b5cee23e8b0 (diff)
downloadmullvadvpn-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.rs142
-rw-r--r--openvpn_ffi/src/parse.rs142
-rw-r--r--openvpn_ffi/src/structs.rs49
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,
+}