openssl_errors/
lib.rs

1//! Custom error library support for the `openssl` crate.
2//!
3//! OpenSSL allows third-party libraries to integrate with its error API. This crate provides a safe interface to that.
4//!
5//! # Examples
6//!
7//! ```
8//! use openssl_errors::{openssl_errors, put_error};
9//! use openssl::error::Error;
10//!
11//! // Errors are organized at the top level into "libraries". The
12//! // openssl_errors! macro can define these.
13//! //
14//! // Libraries contain a set of functions and reasons. The library itself,
15//! // its functions, and its definitions all all have an associated message
16//! // string. This string is what's shown in OpenSSL errors.
17//! //
18//! // The macro creates a type for each library with associated constants for
19//! // its functions and reasons.
20//! openssl_errors! {
21//!     pub library MyLib("my cool library") {
22//!         functions {
23//!             FIND_PRIVATE_KEY("find_private_key");
24//!         }
25//!
26//!         reasons {
27//!             IO_ERROR("IO error");
28//!             BAD_PASSWORD("invalid private key password");
29//!         }
30//!     }
31//! }
32//!
33//! // The put_error! macro pushes errors onto the OpenSSL error stack.
34//! put_error!(MyLib::FIND_PRIVATE_KEY, MyLib::BAD_PASSWORD);
35//!
36//! // Prints `error:80001002:my cool library:find_private_key:invalid private key password:src/lib.rs:27:`
37//! println!("{}", Error::get().unwrap());
38//!
39//! // You can also optionally attach an extra string of context using the
40//! // standard Rust format syntax.
41//! let tries = 2;
42//! put_error!(MyLib::FIND_PRIVATE_KEY, MyLib::IO_ERROR, "tried {} times", tries);
43//!
44//! // Prints `error:80001001:my cool library:find_private_key:IO error:src/lib.rs:34:tried 2 times`
45//! println!("{}", Error::get().unwrap());
46//! ```
47#![warn(missing_docs)]
48#![doc(html_root_url = "https://docs.rs/openssl-errors/0.1")]
49
50use cfg_if::cfg_if;
51use libc::{c_char, c_int};
52use std::borrow::Cow;
53use std::marker::PhantomData;
54use std::ptr;
55
56#[doc(hidden)]
57pub mod export {
58    pub use libc::{c_char, c_int};
59    pub use openssl_sys::{
60        init, ERR_get_next_error_library, ERR_load_strings, ERR_PACK, ERR_STRING_DATA,
61    };
62    pub use std::borrow::Cow;
63    pub use std::option::Option;
64    pub use std::ptr::null;
65    pub use std::sync::Once;
66}
67
68/// An OpenSSL error library.
69pub trait Library {
70    /// Returns the ID assigned to this library by OpenSSL.
71    fn id() -> c_int;
72}
73
74cfg_if! {
75    if #[cfg(ossl300)] {
76        type FunctionInner = *const c_char;
77    } else {
78        type FunctionInner = c_int;
79    }
80}
81
82/// A function declaration, parameterized by its error library.
83pub struct Function<T>(FunctionInner, PhantomData<T>);
84
85// manual impls necessary for the 3.0.0 case
86unsafe impl<T> Sync for Function<T> where T: Sync {}
87unsafe impl<T> Send for Function<T> where T: Send {}
88
89impl<T> Function<T> {
90    /// This is not considered a part of the crate's public API, and is subject to change at any time.
91    ///
92    /// # Safety
93    ///
94    /// The inner value must be valid for the lifetime of the process.
95    #[doc(hidden)]
96    #[inline]
97    pub const unsafe fn __from_raw(raw: FunctionInner) -> Function<T> {
98        Function(raw, PhantomData)
99    }
100
101    /// This is not considered a part of the crate's public API, and is subject to change at any time.
102    #[doc(hidden)]
103    #[inline]
104    pub const fn __as_raw(&self) -> FunctionInner {
105        self.0
106    }
107}
108
109/// A reason declaration, parameterized by its error library.
110pub struct Reason<T>(c_int, PhantomData<T>);
111
112impl<T> Reason<T> {
113    /// This is not considered a part of the crate's public API, and is subject to change at any time.
114    #[doc(hidden)]
115    #[inline]
116    pub const fn __from_raw(raw: c_int) -> Reason<T> {
117        Reason(raw, PhantomData)
118    }
119
120    /// This is not considered a part of the crate's public API, and is subject to change at any time.
121    #[doc(hidden)]
122    #[inline]
123    pub const fn __as_raw(&self) -> c_int {
124        self.0
125    }
126}
127
128/// This is not considered part of this crate's public API. It is subject to change at any time.
129///
130/// # Safety
131///
132/// `file` and `message` must be null-terminated.
133#[doc(hidden)]
134pub unsafe fn __put_error<T>(
135    func: Function<T>,
136    reason: Reason<T>,
137    file: &'static str,
138    line: u32,
139    message: Option<Cow<'static, str>>,
140) where
141    T: Library,
142{
143    put_error_inner(T::id(), func.0, reason.0, file, line, message)
144}
145
146unsafe fn put_error_inner(
147    library: c_int,
148    func: FunctionInner,
149    reason: c_int,
150    file: &'static str,
151    line: u32,
152    message: Option<Cow<'static, str>>,
153) {
154    cfg_if! {
155        if #[cfg(ossl300)] {
156            openssl_sys::ERR_new();
157            openssl_sys::ERR_set_debug(
158                file.as_ptr() as *const c_char,
159                line as c_int,
160                func,
161            );
162            openssl_sys::ERR_set_error(library, reason, ptr::null());
163        } else {
164            openssl_sys::ERR_put_error(
165                library,
166                func,
167                reason,
168                file.as_ptr() as *const c_char,
169                line as c_int,
170            );
171        }
172    }
173
174    let data = match message {
175        Some(Cow::Borrowed(s)) => Some((s.as_ptr() as *const c_char as *mut c_char, 0)),
176        Some(Cow::Owned(s)) => {
177            let ptr = openssl_sys::CRYPTO_malloc(
178                s.len() as _,
179                concat!(file!(), "\0").as_ptr() as *const c_char,
180                line!() as c_int,
181            ) as *mut c_char;
182            if ptr.is_null() {
183                None
184            } else {
185                ptr::copy_nonoverlapping(s.as_ptr(), ptr as *mut u8, s.len());
186                Some((ptr, openssl_sys::ERR_TXT_MALLOCED))
187            }
188        }
189        None => None,
190    };
191    if let Some((ptr, flags)) = data {
192        openssl_sys::ERR_set_error_data(ptr, flags | openssl_sys::ERR_TXT_STRING);
193    }
194}
195
196/// Pushes an error onto the OpenSSL error stack.
197///
198/// A function and reason are required, and must be associated with the same error library. An additional formatted
199/// message string can also optionally be provided.
200#[macro_export]
201macro_rules! put_error {
202    ($function:expr, $reason:expr) => {
203        unsafe {
204            $crate::__put_error(
205                $function,
206                $reason,
207                concat!(file!(), "\0"),
208                line!(),
209                $crate::export::Option::None,
210            );
211        }
212    };
213    ($function:expr, $reason:expr, $message:expr) => {
214        unsafe {
215            $crate::__put_error(
216                $function,
217                $reason,
218                concat!(file!(), "\0"),
219                line!(),
220                // go through format_args to ensure the message string is handled in the same way as the args case
221                $crate::export::Option::Some($crate::export::Cow::Borrowed(
222                    format_args!(concat!($message, "\0")).as_str().unwrap(),
223                )),
224            );
225        }
226    };
227    ($function:expr, $reason:expr, $message:expr, $($args:tt)*) => {
228        unsafe {
229            $crate::__put_error(
230                $function,
231                $reason,
232                concat!(file!(), "\0"),
233                line!(),
234                $crate::export::Option::Some($crate::export::Cow::Owned(
235                    format!(concat!($message, "\0"), $($args)*)),
236                ),
237            );
238        }
239    };
240}
241
242/// Defines custom OpenSSL error libraries.
243///
244/// The created libraries can be used with the `put_error!` macro to create custom OpenSSL errors.
245#[macro_export]
246macro_rules! openssl_errors {
247    ($(
248        $(#[$lib_attr:meta])*
249        $lib_vis:vis library $lib_name:ident($lib_str:expr) {
250            functions {
251                $(
252                    $(#[$func_attr:meta])*
253                    $func_name:ident($func_str:expr);
254                )*
255            }
256
257            reasons {
258                $(
259                    $(#[$reason_attr:meta])*
260                    $reason_name:ident($reason_str:expr);
261                )*
262            }
263        }
264    )*) => {$(
265        $(#[$lib_attr])*
266        $lib_vis enum $lib_name {}
267
268        impl $crate::Library for $lib_name {
269            fn id() -> $crate::export::c_int {
270                static INIT: $crate::export::Once = $crate::export::Once::new();
271                static mut LIB_NUM: $crate::export::c_int = 0;
272                $crate::__openssl_errors_helper! {
273                    @strings $lib_name($lib_str)
274                    functions { $($func_name($func_str);)* }
275                    reasons { $($reason_name($reason_str);)* }
276                }
277
278                unsafe {
279                    INIT.call_once(|| {
280                        $crate::export::init();
281                        LIB_NUM = $crate::export::ERR_get_next_error_library();
282                        STRINGS[0].error = $crate::export::ERR_PACK(LIB_NUM, 0, 0);
283                        $crate::export::ERR_load_strings(LIB_NUM, STRINGS.as_mut_ptr());
284                    });
285
286                    LIB_NUM
287                }
288            }
289        }
290
291        impl $lib_name {
292            $crate::openssl_errors!(@func_consts $lib_name; 1; $($(#[$func_attr])* $func_name($func_str);)*);
293            $crate::openssl_errors!(@reason_consts $lib_name; 1; $($(#[$reason_attr])* $reason_name;)*);
294        }
295    )*};
296    (@func_consts $lib_name:ident; $n:expr; $(#[$attr:meta])* $name:ident($str:expr); $($tt:tt)*) => {
297        $(#[$attr])*
298        pub const $name: $crate::Function<$lib_name> = unsafe {
299            $crate::Function::__from_raw($crate::__openssl_errors_helper!(@func_value $n, $str))
300        };
301        $crate::openssl_errors!(@func_consts $lib_name; $n + 1; $($tt)*);
302    };
303    (@func_consts $lib_name:ident; $n:expr;) => {};
304    (@reason_consts $lib_name:ident; $n:expr; $(#[$attr:meta])* $name:ident; $($tt:tt)*) => {
305        $(#[$attr])*
306        pub const $name: $crate::Reason<$lib_name> = $crate::Reason::__from_raw($n);
307        $crate::openssl_errors!(@reason_consts $lib_name; $n + 1; $($tt)*);
308    };
309    (@reason_consts $lib_name:ident; $n:expr;) => {};
310    (@count $i:ident; $($tt:tt)*) => {
311        1 + $crate::openssl_errors!(@count $($tt)*)
312    };
313    (@count) => { 0 };
314}
315
316cfg_if! {
317    if #[cfg(ossl300)] {
318        #[doc(hidden)]
319        #[macro_export]
320        macro_rules! __openssl_errors_helper {
321            (
322                @strings $lib_name:ident($lib_str:expr)
323                functions { $($func_name:ident($func_str:expr);)* }
324                reasons { $($reason_name:ident($reason_str:expr);)* }
325            ) => {
326                static mut STRINGS: [
327                    $crate::export::ERR_STRING_DATA;
328                    2 + $crate::openssl_errors!(@count $($reason_name;)*)
329                ] = [
330                    $crate::export::ERR_STRING_DATA {
331                        error: 0,
332                        string: concat!($lib_str, "\0").as_ptr() as *const $crate::export::c_char,
333                    },
334                    $(
335                        $crate::export::ERR_STRING_DATA {
336                            error: $crate::export::ERR_PACK(0, 0, $lib_name::$reason_name.__as_raw()),
337                            string: concat!($reason_str, "\0").as_ptr() as *const $crate::export::c_char,
338                        },
339                    )*
340                    $crate::export::ERR_STRING_DATA {
341                        error: 0,
342                        string: $crate::export::null(),
343                    }
344                ];
345            };
346            (@func_value $n:expr, $func_str:expr) => {
347                concat!($func_str, "\0").as_ptr() as *const $crate::export::c_char
348            };
349        }
350    } else {
351        #[doc(hidden)]
352        #[macro_export]
353        macro_rules! __openssl_errors_helper {
354            (
355                @strings $lib_name:ident($lib_str:expr)
356                functions { $($func_name:ident($func_str:expr);)* }
357                reasons { $($reason_name:ident($reason_str:expr);)* }
358            ) => {
359                static mut STRINGS: [
360                    $crate::export::ERR_STRING_DATA;
361                    2 + $crate::openssl_errors!(@count $($func_name;)* $($reason_name;)*)
362                ] = [
363                    $crate::export::ERR_STRING_DATA {
364                        error: 0,
365                        string: concat!($lib_str, "\0").as_ptr() as *const $crate::export::c_char,
366                    },
367                    $(
368                        $crate::export::ERR_STRING_DATA {
369                            error: $crate::export::ERR_PACK(0, $lib_name::$func_name.__as_raw(), 0),
370                            string: concat!($func_str, "\0").as_ptr() as *const $crate::export::c_char,
371                        },
372                    )*
373                    $(
374                        $crate::export::ERR_STRING_DATA {
375                            error: $crate::export::ERR_PACK(0, 0, $lib_name::$reason_name.__as_raw()),
376                            string: concat!($reason_str, "\0").as_ptr() as *const $crate::export::c_char,
377                        },
378                    )*
379                    $crate::export::ERR_STRING_DATA {
380                        error: 0,
381                        string: $crate::export::null(),
382                    }
383                ];
384            };
385            (@func_value $n:expr, $func_str:expr) => {$n};
386        }
387    }
388}