openvpn_plugin/lib.rs
1// Copyright 2023 Mullvad VPN AB.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! `openvpn-plugin` is a crate that makes it easy to write OpenVPN plugins in Rust.
10//!
11//! The crate contains two main things:
12//!
13//! * The `openvpn_plugin!` macro for generating the FFI interface OpenVPN will interact with
14//! * The FFI and safe Rust types needed to communicate with OpenVPN.
15//!
16//! ## Usage
17//!
18//! Edit your `Cargo.toml` to depend on this crate and set the type of your crate to a `cdylib` in
19//! order to make it compile to a shared library that OpenVPN will understand:
20//!
21//! ```toml
22//! [lib]
23//! crate-type = ["cdylib"]
24//!
25//! [dependencies]
26//! openvpn-plugin = "x.y"
27//! ```
28//!
29//! In your crate root (`lib.rs`) define your handle type, the three callback functions and
30//! call the [`openvpn_plugin!`] macro to generate the corresponding FFI bindings.
31//! More details on the handle and the callback functions can be found in the documentation for the
32//! [`openvpn_plugin!`] macro.
33//!
34//! ```rust,no_run
35//! use std::collections::HashMap;
36//! use std::ffi::CString;
37//! use std::io::Error;
38//! use openvpn_plugin::{openvpn_plugin, EventResult, EventType};
39//!
40//! pub struct Handle {
41//!     // Fields needed for the plugin to keep state between callbacks
42//! }
43//!
44//! fn openvpn_open(
45//!     args: Vec<CString>,
46//!     env: HashMap<CString, CString>,
47//! ) -> Result<(Vec<EventType>, Handle), Error> {
48//!     // Listen to only the `Up` event, which will be fired when a tunnel has been established.
49//!     let events = vec![EventType::Up];
50//!     // Create the handle instance.
51//!     let handle = Handle { /* ... */ };
52//!     Ok((events, handle))
53//! }
54//!
55//! fn openvpn_close(handle: Handle) {
56//!     println!("Plugin is closing down");
57//! }
58//!
59//! fn openvpn_event(
60//!     event: EventType,
61//!     args: Vec<CString>,
62//!     env: HashMap<CString, CString>,
63//!     handle: &mut Handle,
64//! ) -> Result<EventResult, Error> {
65//!     /* Process the event */
66//!
67//!     // If the processing worked fine and/or the request the callback represents should be
68//!     // accepted, return EventResult::Success. See EventResult docs for more info.
69//!     Ok(EventResult::Success)
70//! }
71//!
72//! openvpn_plugin!(crate::openvpn_open, crate::openvpn_close, crate::openvpn_event, Handle);
73//! # fn main() {}
74//! ```
75//!
76//! ## Panic handling
77//!
78//! C cannot handle Rust panic unwinding into it, so it is not good practice to let Rust panic when
79//! called from C. Because of this, all calls from this crate to the callbacks given to
80//! [`openvpn_plugin!`] \(`$open_fn`, `$close_fn` and `$event_fn`) are wrapped in
81//! [`catch_unwind`].
82//!
83//! If [`catch_unwind`] captures a panic it will log it and then return
84//! [`OPENVPN_PLUGIN_FUNC_ERROR`] to OpenVPN.
85//!
86//! Note that this will only work for unwinding panics, not with `panic=abort`.
87//!
88//! ## Logging
89//!
90//! Any errors returned from the user defined callbacks or panics that happens anywhere in Rust is
91//! logged by this crate before control is returned to OpenVPN. By default logging happens to
92//! stderr. To activate logging with the `error!` macro in the `log` crate, build this crate with
93//! the `log` feature.
94//!
95//! [`openvpn_plugin!`]: macro.openvpn_plugin.html
96//! [`OPENVPN_PLUGIN_FUNC_ERROR`]: ffi/constant.OPENVPN_PLUGIN_FUNC_ERROR.html
97//! [`catch_unwind`]: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
98
99#[cfg(feature = "serde")]
100#[cfg_attr(feature = "serde", macro_use)]
101extern crate serde;
102
103use std::{
104    collections::HashMap,
105    convert::TryFrom,
106    ffi::CString,
107    fmt,
108    os::raw::{c_int, c_void},
109    panic,
110};
111
112/// FFI types and functions used by the plugin to convert between the types OpenVPN pass and expect
113/// back and the Rust types the plugin will be exposed to.
114///
115/// Not intended for manual use. Is publicly exported since code generated by the `openvpn_plugin`
116/// macro must access these types and functions.
117pub mod ffi;
118
119/// Rust types representing values and instructions from and to OpenVPN. Intended to be the safe
120/// abstraction exposed to the plugins.
121mod types;
122
123/// Functions for logging errors that occur in plugins.
124mod logging;
125
126pub use crate::types::{EventResult, EventType};
127
128/// The main part of this crate. The macro generates the public FFI functions that OpenVPN looks
129/// for in a shared library:
130///
131/// * `openvpn_plugin_open_v3` - Will call `$open_fn`
132/// * `openvpn_plugin_close_v1` - Will call `$close_fn`
133/// * `openvpn_plugin_func_v3` - Will call `$event_fn`
134///
135/// This macro must be called in the crate root of the crate you wish to become an OpenVPN plugin.
136/// That is because the FFI functions must be publicly exported from the shared library for OpenVPN
137/// to find them.
138///
139/// See the top level library documentation and the included `debug-plugin` crate for examples on
140/// how to use this macro.
141///
142///
143/// ## `$open_fn` - The plugin load callback
144///
145/// Should be a function with the following signature:
146///
147/// ```rust,no_run
148/// # use openvpn_plugin::EventType;
149/// # use std::ffi::CString;
150/// # use std::collections::HashMap;
151/// # struct Handle {}
152/// # struct Error {}
153/// fn foo_open(
154///     args: Vec<CString>,
155///     env: HashMap<CString, CString>
156/// ) -> Result<(Vec<EventType>, Handle), Error> {
157///     /// ...
158/// #    unimplemented!();
159/// }
160/// # fn main() {}
161/// ```
162///
163/// With `foo_open` substituted for a function name of your liking and `Handle` being the
164/// `$handle_ty` handle type you pass.
165///
166/// The type of the error in the result from this function does not matter, as long as it implements
167/// `std::error::Error`. Any error returned is logged and then [`OPENVPN_PLUGIN_FUNC_ERROR`]
168/// is returned to OpenVPN, which indicates that the plugin failed to load and OpenVPN will abort
169/// and exit.
170///
171/// This function will be called by OpenVPN when the plugin is loaded, just as OpenVPN starts.
172///
173/// This function has access to the arguments passed to the plugin and the initial
174/// OpenVPN environment. If the plugin deems the open operation successful it should return a vector
175/// with the events it wants to register for and the handle instance that the plugin can use to
176/// keep state (See further down for more on the handle).
177///
178/// The `openvpn_plugin::ffi::parse::{string_array_utf8, env_utf8}` functions can be used to try
179/// to convert the arguments and environment into Rust `String`s.
180///
181///
182/// ## `$close_fn` - The plugin unload callback
183///
184/// Should be a function with the following signature:
185///
186/// ```rust,no_run
187/// # struct Handle {}
188/// fn foo_close(handle: Handle) {
189///     /// ...
190/// #    unimplemented!();
191/// }
192/// # fn main() {}
193/// ```
194///
195/// With `foo_close` substituted for a function name of your liking and `Handle` being the
196/// `$handle_ty` handle type you pass.
197///
198/// This function is called just before the plugin is unloaded, just before OpenVPN shuts down.
199/// Here the plugin can do any cleaning up that is necessary. Since the handle is passed by value it
200/// will be dropped when this function returns.
201///
202///
203/// ## `$event_fn` - The event callback function
204///
205/// Should be a function with the following signature:
206///
207/// ```rust,no_run
208/// # use openvpn_plugin::{EventResult, EventType};
209/// # use std::ffi::CString;
210/// # use std::collections::HashMap;
211/// # struct Handle {}
212/// # struct Error {}
213/// fn foo_event(
214///     event: EventType,
215///     args: Vec<CString>,
216///     env: HashMap<CString, CString>,
217///     handle: &mut Handle,
218/// ) -> Result<EventResult, Error> {
219///     /// ...
220/// #    unimplemented!();
221/// }
222/// # fn main() {}
223/// ```
224///
225/// With `foo_event` substituted for a function name of your liking and `Handle` being the
226/// `$handle_ty` handle type you pass.
227///
228/// The type of the error in the result from this function does not matter, as long as it implements
229/// `std::error::Error`. Any error returned is logged and then [`OPENVPN_PLUGIN_FUNC_ERROR`]
230/// is returned to OpenVPN. [`OPENVPN_PLUGIN_FUNC_ERROR`] indicates different things on different
231/// events. In the case of an authentication request or TLS key verification it means that the
232/// request is denied and the connection is aborted.
233///
234/// This function is being called by OpenVPN each time one of the events that `$open_fn` registered
235/// for happens. This can for example be that a tunnel is established or that a client wants to
236/// authenticate.
237///
238/// The first argument, [`EventType`], will tell which event that is happening.
239///
240///
241/// ## `$handle_ty` - The handle type
242///
243/// The handle must be created and returned by the `$open_fn` function and will be kept for the
244/// entire runtime of the plugin. The handle is passed to every subsequent callback and this is the
245/// way that the plugin is supposed to keep state between each callback.
246///
247/// The handle instance is being dropped upon return from the `$close_fn` function just as the
248/// plugin is being unloaded.
249///
250/// [`EventType`]: types/enum.EventType.html
251/// [`OPENVPN_PLUGIN_FUNC_ERROR`]: ffi/constant.OPENVPN_PLUGIN_FUNC_ERROR.html
252#[macro_export]
253macro_rules! openvpn_plugin {
254    ($open_fn:path, $close_fn:path, $event_fn:path, $handle_ty:ty) => {
255        /// Called by OpenVPN when the plugin is first loaded on OpenVPN start.
256        /// Used to register which events the plugin wants to listen to (`args.type_mask`). Can
257        /// also set an arbitrary pointer inside `args.handle` that will then be passed to all
258        /// subsequent calls to the plugin.
259        ///
260        /// Will parse the data from OpenVPN and call the function given as `$open_fn` to the
261        /// `openvpn_plugin` macro.
262        #[no_mangle]
263        pub unsafe extern "C" fn openvpn_plugin_open_v3(
264            _version: ::std::os::raw::c_int,
265            args: *const $crate::ffi::openvpn_plugin_args_open_in,
266            retptr: *mut $crate::ffi::openvpn_plugin_args_open_return,
267        ) -> ::std::os::raw::c_int {
268            unsafe { $crate::openvpn_plugin_open::<$handle_ty, _, _>(args, retptr, $open_fn) }
269        }
270
271        /// Called by OpenVPN when the plugin is unloaded, just before OpenVPN shuts down.
272        /// Will call the function given as `$event_fn` to the `openvpn_plugin` macro.
273        #[no_mangle]
274        pub unsafe extern "C" fn openvpn_plugin_close_v1(handle: *const ::std::os::raw::c_void) {
275            unsafe { $crate::openvpn_plugin_close::<$handle_ty, _>(handle, $close_fn) }
276        }
277
278        /// Called by OpenVPN for each `OPENVPN_PLUGIN_*` event that it registered for in
279        /// the open function.
280        ///
281        /// Will parse the data from OpenVPN and call the function given as `$event_fn` to the
282        /// `openvpn_plugin` macro.
283        #[no_mangle]
284        pub unsafe extern "C" fn openvpn_plugin_func_v3(
285            _version: ::std::os::raw::c_int,
286            args: *const $crate::ffi::openvpn_plugin_args_func_in,
287            _retptr: *const $crate::ffi::openvpn_plugin_args_func_return,
288        ) -> ::std::os::raw::c_int {
289            unsafe { $crate::openvpn_plugin_func::<$handle_ty, _, _>(args, $event_fn) }
290        }
291    };
292}
293
294
295/// Internal macro for matching on a result and either return the value inside the `Ok`, or in the
296/// case of an `Err`, log it and early return [`OPENVPN_PLUGIN_FUNC_ERROR`].
297///
298/// [`OPENVPN_PLUGIN_FUNC_ERROR`]: ffi/constant.OPENVPN_PLUGIN_FUNC_ERROR.html
299macro_rules! try_or_return_error {
300    ($result:expr, $error_msg:expr) => {
301        match $result {
302            Ok(result) => result,
303            Err(e) => {
304                logging::log_error(&Error::new($error_msg, e));
305                return ffi::OPENVPN_PLUGIN_FUNC_ERROR;
306            }
307        }
308    };
309}
310
311
312/// Internal helper function. This function should never be called manually, only by code generated
313/// by the [`openvpn_plugin!`] macro.
314///
315/// [`openvpn_plugin!`]: macro.openvpn_plugin.html
316#[doc(hidden)]
317pub unsafe fn openvpn_plugin_open<H, E, F>(
318    args: *const ffi::openvpn_plugin_args_open_in,
319    retptr: *mut ffi::openvpn_plugin_args_open_return,
320    open_fn: F,
321) -> c_int
322where
323    E: ::std::error::Error,
324    F: panic::RefUnwindSafe,
325    F: Fn(Vec<CString>, HashMap<CString, CString>) -> Result<(Vec<EventType>, H), E>,
326{
327    let parsed_args = try_or_return_error!(
328        ffi::parse::string_array((*args).argv),
329        "Malformed args from OpenVPN"
330    );
331    let parsed_env =
332        try_or_return_error!(ffi::parse::env((*args).envp), "Malformed env from OpenVPN");
333
334    match panic::catch_unwind(|| open_fn(parsed_args, parsed_env)) {
335        Ok(Ok((events, handle))) => {
336            (*retptr).type_mask = types::events_to_bitmask(&events);
337            (*retptr).handle = Box::into_raw(Box::new(handle)) as *const c_void;
338            ffi::OPENVPN_PLUGIN_FUNC_SUCCESS
339        }
340        Ok(Err(e)) => {
341            logging::log_error(&e);
342            ffi::OPENVPN_PLUGIN_FUNC_ERROR
343        }
344        Err(e) => {
345            logging::log_panic("plugin open", &e);
346            ffi::OPENVPN_PLUGIN_FUNC_ERROR
347        }
348    }
349}
350
351
352/// Internal helper function. This function should never be called manually, only by code generated
353/// by the [`openvpn_plugin!`] macro.
354///
355/// [`openvpn_plugin!`]: macro.openvpn_plugin.html
356#[doc(hidden)]
357pub unsafe fn openvpn_plugin_close<H, F>(handle: *const c_void, close_fn: F)
358where
359    H: panic::UnwindSafe,
360    F: Fn(H) + panic::RefUnwindSafe,
361{
362    // IMPORTANT: Bring the handle object back from a raw pointer. This will cause the
363    // handle object to be properly deallocated when `$close_fn` returns.
364    let handle = *Box::from_raw(handle as *mut H);
365    if let Err(e) = panic::catch_unwind(|| close_fn(handle)) {
366        logging::log_panic("plugin close", &e);
367    }
368}
369
370
371/// Internal helper function. This function should never be called manually, only by code generated
372/// by the [`openvpn_plugin!`] macro.
373///
374/// [`openvpn_plugin!`]: macro.openvpn_plugin.html
375#[doc(hidden)]
376pub unsafe fn openvpn_plugin_func<H, E, F>(
377    args: *const ffi::openvpn_plugin_args_func_in,
378    event_fn: F,
379) -> c_int
380where
381    E: ::std::error::Error,
382    F: panic::RefUnwindSafe,
383    F: Fn(EventType, Vec<CString>, HashMap<CString, CString>, &mut H) -> Result<EventResult, E>,
384{
385    let event_type = (*args).event_type;
386    let event = try_or_return_error!(
387        EventType::try_from(event_type).map_err(|_| InvalidEventType(event_type)),
388        "Invalid event integer"
389    );
390    let parsed_args = try_or_return_error!(
391        ffi::parse::string_array((*args).argv),
392        "Malformed args from OpenVPN"
393    );
394    let parsed_env =
395        try_or_return_error!(ffi::parse::env((*args).envp), "Malformed env from OpenVPN");
396
397    let result = panic::catch_unwind(|| {
398        let handle: &mut H = &mut *((*args).handle as *mut H);
399        event_fn(event, parsed_args, parsed_env, handle)
400    });
401
402    match result {
403        Ok(Ok(EventResult::Success)) => ffi::OPENVPN_PLUGIN_FUNC_SUCCESS,
404        Ok(Ok(EventResult::Deferred)) => ffi::OPENVPN_PLUGIN_FUNC_DEFERRED,
405        Ok(Ok(EventResult::Failure)) => ffi::OPENVPN_PLUGIN_FUNC_ERROR,
406        Ok(Err(e)) => {
407            logging::log_error(&e);
408            ffi::OPENVPN_PLUGIN_FUNC_ERROR
409        }
410        Err(e) => {
411            logging::log_panic("plugin func", &e);
412            ffi::OPENVPN_PLUGIN_FUNC_ERROR
413        }
414    }
415}
416
417
418/// Internal error type
419#[derive(Debug)]
420struct Error {
421    msg: &'static str,
422    source: Box<dyn (::std::error::Error)>,
423}
424
425impl Error {
426    pub(crate) fn new(msg: &'static str, source: impl std::error::Error + 'static) -> Error {
427        Error {
428            msg,
429            source: Box::new(source) as Box<dyn std::error::Error>,
430        }
431    }
432}
433
434impl fmt::Display for Error {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        self.msg.fmt(f)
437    }
438}
439
440impl std::error::Error for Error {
441    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
442        Some(self.source.as_ref())
443    }
444}
445
446/// Error thrown when trying to convert from an invalid integer into an `Event`.
447#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
448struct InvalidEventType(c_int);
449
450impl fmt::Display for InvalidEventType {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        write!(f, "{} is not a valid OPENVPN_PLUGIN_* constant", self.0)
453    }
454}
455
456impl std::error::Error for InvalidEventType {}