use std::{
ffi::{c_char, c_int, c_long, c_ulong, c_void},
sync::OnceLock,
};
use libloading::{AsFilename, Library};
use crate::sys::*;
static OPENSSL: OnceLock<OpenSSL> = OnceLock::new();
pub enum Error {
Loader(libloading::Error),
AlreadyLoaded,
VersionTooOld,
}
impl From<libloading::Error> for Error {
fn from(value: libloading::Error) -> Self {
Self::Loader(value)
}
}
#[allow(bad_style)]
pub struct OpenSSL {
lib: Library,
pub version_num: c_ulong,
pub BIO_meth_new: unsafe extern "C" fn(c_int, *const c_char) -> *mut BIO_METHOD,
pub BIO_meth_free: unsafe extern "C" fn(*mut BIO_METHOD),
pub BIO_meth_set_write: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO, *const c_char, c_int) -> c_int>,
) -> c_int,
pub BIO_meth_set_read: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO, *mut c_char, c_int) -> c_int>,
) -> c_int,
pub BIO_meth_set_puts: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO, *const c_char) -> c_int>,
) -> c_int,
pub BIO_meth_set_ctrl: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO, c_int, c_long, *mut c_void) -> c_long>,
) -> c_int,
pub BIO_meth_set_create: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO) -> c_int>,
) -> c_int,
pub BIO_meth_set_destroy: unsafe extern "C" fn(
*mut BIO_METHOD,
Option<unsafe extern "C" fn(*mut BIO) -> c_int>,
) -> c_int,
pub BIO_new: unsafe extern "C" fn(*const BIO_METHOD) -> *mut BIO,
pub BIO_get_data: unsafe extern "C" fn(*mut BIO) -> *mut c_void,
pub BIO_set_data: unsafe extern "C" fn(*mut BIO, *mut c_void),
pub BIO_set_init: unsafe extern "C" fn(*mut BIO, c_int),
pub BIO_set_flags: unsafe extern "C" fn(*mut BIO, c_int),
pub BIO_clear_flags: unsafe extern "C" fn(*mut BIO, c_int),
pub SSL_new: unsafe extern "C" fn(*mut SSL_CTX) -> *mut SSL,
pub SSL_free: unsafe extern "C" fn(*mut SSL),
pub SSL_connect: unsafe extern "C" fn(*mut SSL) -> i32,
pub SSL_accept: unsafe extern "C" fn(ssl: *mut SSL) -> c_int,
pub SSL_ctrl: unsafe extern "C" fn(*mut SSL, c_int, c_long, *mut c_void) -> c_long,
pub SSL_do_handshake: unsafe extern "C" fn(*mut SSL) -> c_int,
pub SSL_set_bio: unsafe extern "C" fn(*mut SSL, *mut BIO, *mut BIO),
pub SSL_get_rbio: unsafe extern "C" fn(*mut SSL) -> *mut BIO,
pub SSL_get_error: unsafe extern "C" fn(*mut SSL, c_int) -> c_int,
pub SSL_read_ex: unsafe extern "C" fn(*mut SSL, *mut c_void, usize, *mut usize) -> c_int,
pub SSL_write_ex: unsafe extern "C" fn(
ssl: *mut SSL,
buf: *const c_void,
num: usize,
written: *mut usize,
) -> c_int,
pub ERR_get_error_all: Option<
unsafe extern "C" fn(
file: *mut *const c_char,
line: *mut c_int,
func: *mut *const c_char,
data: *mut *const c_char,
flags: *mut c_int,
) -> c_ulong,
>,
pub ERR_get_error_line_data: Option<
unsafe extern "C" fn(
file: *mut *const c_char,
line: *mut c_int,
data: *mut *const c_char,
flags: *mut c_int,
) -> c_ulong,
>,
pub ERR_func_error_string: Option<unsafe extern "C" fn(err: c_ulong) -> *const c_char>,
pub ERR_lib_error_string: unsafe extern "C" fn(err: c_ulong) -> *const c_char,
pub ERR_reason_error_string: unsafe extern "C" fn(err: c_ulong) -> *const c_char,
}
impl OpenSSL {
fn load(filename: impl AsFilename) -> Result<Self, Error> {
let lib = {
#[cfg(windows)]
{
libloading::os::windows::Library::open_already_loaded(filename)?
}
#[cfg(unix)]
{
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "hurd",
))] {
const RTLD_NOLOAD: c_int = 0x4;
} else if #[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
target_os = "cygwin",
))] {
const RTLD_NOLOAD: c_int = 0x10;
} else if #[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
))] {
const RTLD_NOLOAD: c_int = 0x2000;
} else {
compile_error!(
"Target has no known `RTLD_NOLOAD` value. Please submit an issue or PR adding it."
);
}
}
unsafe { libloading::os::unix::Library::open(Some(filename), RTLD_NOLOAD)? }
}
};
let mut rv = Self {
BIO_meth_new: *unsafe { lib.get(b"BIO_meth_new")? },
BIO_meth_free: *unsafe { lib.get(b"BIO_meth_free")? },
BIO_meth_set_write: *unsafe { lib.get(b"BIO_meth_set_write")? },
BIO_meth_set_read: *unsafe { lib.get(b"BIO_meth_set_read")? },
BIO_meth_set_puts: *unsafe { lib.get(b"BIO_meth_set_puts")? },
BIO_meth_set_ctrl: *unsafe { lib.get(b"BIO_meth_set_ctrl")? },
BIO_meth_set_create: *unsafe { lib.get(b"BIO_meth_set_create")? },
BIO_meth_set_destroy: *unsafe { lib.get(b"BIO_meth_set_destroy")? },
BIO_new: *unsafe { lib.get(b"BIO_new")? },
BIO_get_data: *unsafe { lib.get(b"BIO_get_data")? },
BIO_set_data: *unsafe { lib.get(b"BIO_set_data")? },
BIO_set_init: *unsafe { lib.get(b"BIO_set_init")? },
BIO_set_flags: *unsafe { lib.get(b"BIO_set_flags")? },
BIO_clear_flags: *unsafe { lib.get(b"BIO_clear_flags")? },
SSL_new: *unsafe { lib.get(b"SSL_new")? },
SSL_free: *unsafe { lib.get(b"SSL_free")? },
SSL_connect: *unsafe { lib.get(b"SSL_connect")? },
SSL_accept: *unsafe { lib.get(b"SSL_accept")? },
SSL_ctrl: *unsafe { lib.get(b"SSL_ctrl")? },
SSL_do_handshake: *unsafe { lib.get(b"SSL_do_handshake")? },
SSL_set_bio: *unsafe { lib.get(b"SSL_set_bio")? },
SSL_get_rbio: *unsafe { lib.get(b"SSL_get_rbio")? },
SSL_get_error: *unsafe { lib.get(b"SSL_get_error")? },
SSL_read_ex: *unsafe { lib.get(b"SSL_read_ex")? },
SSL_write_ex: *unsafe { lib.get(b"SSL_write_ex")? },
ERR_get_error_all: None,
ERR_get_error_line_data: None,
ERR_func_error_string: None,
ERR_lib_error_string: *unsafe { lib.get(b"ERR_lib_error_string")? },
ERR_reason_error_string: *unsafe { lib.get(b"ERR_reason_error_string")? },
version_num: unsafe {
lib.get::<unsafe extern "C" fn() -> c_ulong>(b"OpenSSL_version_num")?()
},
lib: lib.into(),
};
if rv.version_num < 0x10100010 {
return Err(Error::VersionTooOld);
}
if rv.version_num < 0x30000000 {
rv.ERR_get_error_line_data = Some(*unsafe { rv.lib.get(b"ERR_get_error_line_data")? });
rv.ERR_func_error_string = Some(*unsafe { rv.lib.get(b"ERR_func_error_string")? });
} else {
rv.ERR_get_error_all = Some(*unsafe { rv.lib.get(b"ERR_get_error_all")? });
}
Ok(rv)
}
}
pub fn load(filename: impl AsFilename) -> Result<(), Error> {
if OPENSSL.get().is_some() {
return Err(Error::AlreadyLoaded);
}
OPENSSL
.set(OpenSSL::load(filename)?)
.map_err(|_| Error::AlreadyLoaded)
}
pub fn get() -> &'static OpenSSL {
OPENSSL.get().expect("OpenSSL library not loaded")
}