#![allow(clippy::needless_doctest_main)]
pub use gsasl_sys::Gsasl_rc::*;
use gsasl_sys::*;
use std::ffi::{CStr, CString};
use std::ptr::{self, null_mut};
pub use discard::{Discard, DiscardOnDrop};
pub mod buffer;
mod callback;
pub mod error;
mod mechanisms;
pub mod session;
pub use buffer::SaslString;
pub use callback::Callback;
pub use mechanisms::Mechanisms;
pub use session::Session;
pub use gsasl_sys::{self as sys, Gsasl_property as Property, Gsasl_rc as ReturnCode};
pub use session::Step;
pub use error::{rsasl_err_to_str, rsasl_errname_to_str, SaslError};
#[derive(Debug)]
pub struct SASL<D, E> {
ctx: Box<Gsasl>,
appdata: std::marker::PhantomData<D>,
sessdata: std::marker::PhantomData<E>,
}
pub type RSASL<D, E> = DiscardOnDrop<SASL<D, E>>;
impl<D, E> SASL<D, E> {
pub fn new() -> error::Result<DiscardOnDrop<Self>> {
Ok(DiscardOnDrop::new(Self::init()?))
}
pub(crate) fn from_ptr(ctx: *mut Gsasl) -> Self {
Self {
ctx: unsafe { Box::from_raw(ctx) },
appdata: std::marker::PhantomData,
sessdata: std::marker::PhantomData,
}
}
fn init() -> error::Result<Self> {
let mut ptr = null_mut();
let res = unsafe { gsasl_init(&mut ptr) };
if res != (GSASL_OK as libc::c_int) {
return Err(error::SaslError(res));
}
Ok(Self::from_ptr(ptr))
}
pub fn client_mech_list(&mut self) -> error::Result<Mechanisms> {
let mut out = ptr::null_mut();
let ret = unsafe {
gsasl_client_mechlist(
self.ctx.as_mut() as *mut Gsasl,
(&mut out) as *mut *mut libc::c_char,
)
};
if ret != (GSASL_OK as libc::c_int) {
Err(error::SaslError(ret))
} else {
let s = unsafe { SaslString::from_raw(out) };
Ok(Mechanisms::from_sasl(s))
}
}
pub fn server_mech_list(&mut self) -> error::Result<Mechanisms> {
let mut out = ptr::null_mut();
let ret = unsafe {
gsasl_server_mechlist(
self.ctx.as_mut() as *mut Gsasl,
&mut out as *mut *mut libc::c_char,
)
};
if ret != (GSASL_OK as libc::c_int) {
Err(error::SaslError(ret))
} else {
let s = unsafe { SaslString::from_raw(out) };
Ok(Mechanisms::from_sasl(s))
}
}
pub fn suggest_client_mechanism(&mut self, mechs: Mechanisms) -> Result<&str, SaslError> {
unsafe {
let ptr =
gsasl_client_suggest_mechanism(self.ctx.as_mut() as *mut Gsasl, mechs.as_raw_ptr());
if ptr.is_null() {
Err(SaslError(GSASL_UNKNOWN_MECHANISM as libc::c_int))
} else {
let cstr = CStr::from_ptr(ptr);
cstr.to_str().map_err(|_: std::str::Utf8Error| {
SaslError(GSASL_MECHANISM_PARSE_ERROR as libc::c_int)
})
}
}
}
pub fn client_supports(&mut self, mech: &CStr) -> bool {
let ret = unsafe {
gsasl_client_support_p(
self.ctx.as_mut() as *mut Gsasl,
mech.to_bytes_with_nul().as_ptr() as *const libc::c_char,
)
};
ret == 1
}
pub fn server_supports(&mut self, mech: &CStr) -> bool {
let ret = unsafe {
gsasl_server_support_p(
self.ctx.as_mut() as *mut Gsasl,
mech.as_ptr() as *const libc::c_char,
)
};
ret == 1
}
pub fn install_callback<C: Callback<D, E>>(&mut self) {
self.install_callback_raw(Some(callback::wrap::<C, D, E>));
}
fn install_callback_raw(&mut self, callback: Gsasl_callback_function) {
unsafe {
gsasl_callback_set(self.ctx.as_mut() as *mut Gsasl, callback);
}
}
pub fn client_start(&mut self, mech: &str) -> error::Result<DiscardOnDrop<Session<E>>> {
let mut ptr: *mut Gsasl_session = ptr::null_mut();
let cmech = CString::new(mech)
.map_err(|_| SaslError(ReturnCode::GSASL_MECHANISM_PARSE_ERROR as libc::c_int))?;
let res = unsafe {
gsasl_client_start(
self.ctx.as_mut() as *mut Gsasl,
cmech.as_ptr(),
&mut ptr as *mut *mut Gsasl_session,
)
};
if res != (GSASL_OK as libc::c_int) {
Err(error::SaslError(res))
} else {
let session = Session::from_ptr(ptr);
Ok(DiscardOnDrop::new(session))
}
}
pub fn server_start(&mut self, mech: &str) -> error::Result<DiscardOnDrop<Session<E>>> {
let mut ptr: *mut Gsasl_session = ptr::null_mut();
let cmech = CString::new(mech)
.map_err(|_| SaslError(ReturnCode::GSASL_MECHANISM_PARSE_ERROR as libc::c_int))?;
let res = unsafe {
gsasl_server_start(
self.ctx.as_mut() as *mut Gsasl,
cmech.as_ptr(),
&mut ptr as *mut *mut Gsasl_session,
)
};
if res != (GSASL_OK as libc::c_int) {
Err(error::SaslError(res))
} else {
let session = Session::from_ptr(ptr);
Ok(DiscardOnDrop::new(session))
}
}
pub fn store(&mut self, data: Box<D>) {
unsafe {
gsasl_callback_hook_set(
self.ctx.as_mut() as *mut Gsasl,
Box::into_raw(data) as *mut libc::c_void,
)
}
}
pub unsafe fn retrieve(&mut self) -> Option<Box<D>> {
let ptr = gsasl_callback_hook_get(self.ctx.as_mut() as *mut Gsasl);
gsasl_callback_hook_set(self.ctx.as_mut() as *mut Gsasl, std::ptr::null_mut());
if !ptr.is_null() {
Some(Box::from_raw(ptr as *mut D))
} else {
None
}
}
pub fn retrieve_mut(&mut self) -> Option<&mut D> {
unsafe {
let ptr = gsasl_callback_hook_get(self.ctx.as_mut() as *mut Gsasl) as *mut D;
ptr.as_mut()
}
}
pub fn callback(&mut self, session: &mut Session<E>, prop: Property) -> libc::c_int {
unsafe { gsasl_callback(self.ctx.as_mut() as *mut Gsasl, session.as_ptr(), prop) }
}
pub(crate) unsafe fn done(self) {
gsasl_done(Box::leak(self.ctx) as *mut Gsasl);
}
}
impl SASL<(), ()> {
pub fn new_untyped() -> error::Result<DiscardOnDrop<Self>> {
SASL::new()
}
}
impl<D, E> Discard for SASL<D, E> {
fn discard(mut self) {
unsafe {
self.retrieve();
self.done();
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::iter::FromIterator;
#[test]
fn callback_test() {
struct CB;
impl Callback<u32, u64> for CB {
fn callback(
sasl: &mut SASL<u32, u64>,
session: &mut Session<u64>,
_prop: Property,
) -> Result<(), ReturnCode> {
assert_eq!(sasl.retrieve_mut(), Some(&mut 0x55555555));
assert_eq!(session.retrieve_mut(), Some(&mut 0xAAAAAAAAAAAAAAAA));
Ok(())
}
}
let mut sasl = SASL::new().unwrap();
sasl.install_callback::<CB>();
sasl.store(Box::new(0x55555555));
let mut session = sasl.client_start("PLAIN").unwrap();
session.store(Box::new(0xAAAAAAAAAAAAAAAA));
assert_eq!(
GSASL_OK as libc::c_int,
sasl.callback(&mut session, Property::GSASL_VALIDATE_SIMPLE)
);
}
#[test]
fn callback_unset_test() {
struct CB;
impl Callback<u32, u64> for CB {
fn callback(
sasl: &mut SASL<u32, u64>,
session: &mut Session<u64>,
_prop: Property,
) -> Result<(), ReturnCode> {
assert_eq!(sasl.retrieve_mut(), None);
assert_eq!(session.retrieve_mut(), None);
Ok(())
}
}
let mut sasl = SASL::new().unwrap();
sasl.install_callback::<CB>();
let mut session = sasl.client_start("PLAIN").unwrap();
assert_eq!(
GSASL_OK as libc::c_int,
sasl.callback(&mut session, Property::GSASL_VALIDATE_SIMPLE)
);
}
#[test]
fn suggest_good() {
let mechs = vec!["PLAIN", "GSSAPI", "INVALID", "SCRAM-SHA-256"];
let mut sasl = SASL::new_untyped().unwrap();
let suggest = sasl.suggest_client_mechanism(Mechanisms::from_iter(mechs.iter()));
assert!(suggest.is_ok());
assert_eq!("GSSAPI", suggest.unwrap());
}
#[test]
fn suggest_fail_on_invalid() {
let mechs = vec!["INVALID", "ALSOINV", "MOREINV3"];
let mut sasl = SASL::new_untyped().unwrap();
let suggest = sasl.suggest_client_mechanism(Mechanisms::from_iter(mechs.iter()));
assert_eq!(
Err(SaslError(GSASL_UNKNOWN_MECHANISM as libc::c_int)),
suggest
);
}
}