#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::upper_case_acronyms)]
#![allow(dead_code)]
use std::ffi::c_void;
use std::ptr;
pub(super) type SECURITY_STATUS = i32;
type WCHAR = u16;
type LPCWSTR = *const WCHAR;
type LPWSTR = *mut WCHAR;
type ULONG = u32;
type PULONG = *mut ULONG;
pub(super) type PVOID = *mut c_void;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct TimeStamp {
pub(super) LowPart: u32,
pub(super) HighPart: i32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct CredHandle {
pub(super) dwLower: usize,
pub(super) dwUpper: usize,
}
impl CredHandle {
fn is_valid(&self) -> bool {
self.dwLower != 0 || self.dwUpper != 0
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct CtxtHandle {
pub(super) dwLower: usize,
pub(super) dwUpper: usize,
}
impl CtxtHandle {
pub(super) fn is_valid(&self) -> bool {
self.dwLower != 0 || self.dwUpper != 0
}
}
#[repr(C)]
#[derive(Debug)]
pub(super) struct SecBuffer {
pub(super) cbBuffer: ULONG,
pub(super) BufferType: ULONG,
pub(super) pvBuffer: PVOID,
}
impl Default for SecBuffer {
fn default() -> Self {
Self {
cbBuffer: 0,
BufferType: SECBUFFER_EMPTY,
pvBuffer: ptr::null_mut(),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub(super) struct SecBufferDesc {
pub(super) ulVersion: ULONG,
pub(super) cBuffers: ULONG,
pub(super) pBuffers: *mut SecBuffer,
}
impl Default for SecBufferDesc {
fn default() -> Self {
Self {
ulVersion: SECBUFFER_VERSION,
cBuffers: 0,
pBuffers: ptr::null_mut(),
}
}
}
#[repr(C)]
struct SEC_WINNT_AUTH_IDENTITY_W {
User: LPWSTR,
UserLength: ULONG,
Domain: LPWSTR,
DomainLength: ULONG,
Password: LPWSTR,
PasswordLength: ULONG,
Flags: ULONG,
}
#[repr(C)]
struct SecPkgInfoW {
fCapabilities: ULONG,
wVersion: u16,
wRPCID: u16,
cbMaxToken: ULONG,
Name: LPWSTR,
Comment: LPWSTR,
}
pub(super) const SEC_E_OK: SECURITY_STATUS = 0;
const SEC_I_CONTINUE_NEEDED: SECURITY_STATUS = 0x00090312_u32 as i32;
const SEC_I_COMPLETE_NEEDED: SECURITY_STATUS = 0x00090313_u32 as i32;
const SEC_I_COMPLETE_AND_CONTINUE: SECURITY_STATUS = 0x00090314_u32 as i32;
const SEC_E_LOGON_DENIED: SECURITY_STATUS = 0x8009030C_u32 as i32;
pub(super) const SEC_E_TARGET_UNKNOWN: SECURITY_STATUS = 0x80090303_u32 as i32;
const SEC_E_INVALID_HANDLE: SECURITY_STATUS = 0x80090301_u32 as i32;
const SEC_E_INVALID_TOKEN: SECURITY_STATUS = 0x80090308_u32 as i32;
pub(super) const SEC_E_NO_CREDENTIALS: SECURITY_STATUS = 0x8009030E_u32 as i32;
const SEC_E_CONTEXT_EXPIRED: SECURITY_STATUS = 0x80090317_u32 as i32;
const SEC_E_INTERNAL_ERROR: SECURITY_STATUS = 0x80090304_u32 as i32;
const SEC_E_INSUFFICIENT_MEMORY: SECURITY_STATUS = 0x80090300_u32 as i32;
const SEC_E_BUFFER_TOO_SMALL: SECURITY_STATUS = 0x80090321_u32 as i32;
const SEC_E_WRONG_PRINCIPAL: SECURITY_STATUS = 0x80090322_u32 as i32;
const SEC_E_UNSUPPORTED_FUNCTION: SECURITY_STATUS = 0x80090302_u32 as i32;
const SECPKG_CRED_OUTBOUND: ULONG = 2;
const SECPKG_CRED_INBOUND: ULONG = 1;
const SECPKG_CRED_BOTH: ULONG = 3;
const ISC_REQ_DELEGATE: ULONG = 0x00000001;
const ISC_REQ_MUTUAL_AUTH: ULONG = 0x00000002;
const ISC_REQ_REPLAY_DETECT: ULONG = 0x00000004;
const ISC_REQ_SEQUENCE_DETECT: ULONG = 0x00000008;
const ISC_REQ_CONFIDENTIALITY: ULONG = 0x00000010;
const ISC_REQ_USE_SUPPLIED_CREDS: ULONG = 0x00000080;
const ISC_REQ_ALLOCATE_MEMORY: ULONG = 0x00000100;
const ISC_REQ_DATAGRAM: ULONG = 0x00000400;
const ISC_REQ_CONNECTION: ULONG = 0x00000800;
const ISC_REQ_EXTENDED_ERROR: ULONG = 0x00004000;
const ISC_REQ_STREAM: ULONG = 0x00008000;
const ISC_REQ_INTEGRITY: ULONG = 0x00010000;
pub(super) const STANDARD_CONTEXT_REQ: ULONG =
ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH | ISC_REQ_INTEGRITY | ISC_REQ_EXTENDED_ERROR;
const SECBUFFER_EMPTY: ULONG = 0;
pub(super) const SECBUFFER_TOKEN: ULONG = 2;
const SECBUFFER_PKG_PARAMS: ULONG = 3;
const SECBUFFER_MISSING: ULONG = 4;
const SECBUFFER_EXTRA: ULONG = 5;
const SECBUFFER_STREAM_TRAILER: ULONG = 6;
const SECBUFFER_STREAM_HEADER: ULONG = 7;
pub(super) const SECBUFFER_CHANNEL_BINDINGS: ULONG = 14;
const SECBUFFER_TARGET_HOST: ULONG = 16;
pub(super) const SECBUFFER_VERSION: ULONG = 0;
const SEC_WINNT_AUTH_IDENTITY_ANSI: ULONG = 1;
const SEC_WINNT_AUTH_IDENTITY_UNICODE: ULONG = 2;
#[link(name = "secur32")]
unsafe extern "system" {
fn AcquireCredentialsHandleW(
pszPrincipal: LPCWSTR,
pszPackage: LPCWSTR,
fCredentialUse: ULONG,
pvLogonId: PVOID,
pAuthData: PVOID,
pGetKeyFn: PVOID,
pvGetKeyArgument: PVOID,
phCredential: *mut CredHandle,
ptsExpiry: *mut TimeStamp,
) -> SECURITY_STATUS;
pub(super) fn InitializeSecurityContextW(
phCredential: *const CredHandle,
phContext: *const CtxtHandle,
pszTargetName: LPCWSTR,
fContextReq: ULONG,
Reserved1: ULONG,
TargetDataRep: ULONG,
pInput: *const SecBufferDesc,
Reserved2: ULONG,
phNewContext: *mut CtxtHandle,
pOutput: *mut SecBufferDesc,
pfContextAttr: PULONG,
ptsExpiry: *mut TimeStamp,
) -> SECURITY_STATUS;
pub(super) fn DeleteSecurityContext(phContext: *mut CtxtHandle) -> SECURITY_STATUS;
pub(super) fn FreeCredentialsHandle(phCredential: *mut CredHandle) -> SECURITY_STATUS;
fn FreeContextBuffer(pvContextBuffer: PVOID) -> SECURITY_STATUS;
fn QuerySecurityPackageInfoW(
pszPackageName: LPCWSTR,
ppPackageInfo: *mut *mut SecPkgInfoW,
) -> SECURITY_STATUS;
pub(super) fn CompleteAuthToken(
phContext: *const CtxtHandle,
pToken: *const SecBufferDesc,
) -> SECURITY_STATUS;
}
pub(super) fn get_sspi_error_message(status: SECURITY_STATUS) -> String {
match status {
SEC_E_OK => "Success".to_string(),
SEC_I_CONTINUE_NEEDED => "Continue needed".to_string(),
SEC_I_COMPLETE_NEEDED => "Complete needed".to_string(),
SEC_I_COMPLETE_AND_CONTINUE => "Complete and continue".to_string(),
SEC_E_LOGON_DENIED => "Logon denied".to_string(),
SEC_E_TARGET_UNKNOWN => "Target unknown (SPN not found)".to_string(),
SEC_E_INVALID_HANDLE => "Invalid handle".to_string(),
SEC_E_INVALID_TOKEN => "Invalid token".to_string(),
SEC_E_NO_CREDENTIALS => "No credentials available".to_string(),
SEC_E_CONTEXT_EXPIRED => "Context expired".to_string(),
SEC_E_INTERNAL_ERROR => "Internal error".to_string(),
SEC_E_INSUFFICIENT_MEMORY => "Insufficient memory".to_string(),
SEC_E_BUFFER_TOO_SMALL => "Buffer too small".to_string(),
SEC_E_WRONG_PRINCIPAL => "Wrong principal".to_string(),
SEC_E_UNSUPPORTED_FUNCTION => "Unsupported function".to_string(),
_ => format!("SSPI error 0x{:08X}", status as u32),
}
}
pub(super) fn to_wide_string(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub(super) fn is_success_status(status: SECURITY_STATUS) -> bool {
status >= 0
}
pub(super) fn needs_continue(status: SECURITY_STATUS) -> bool {
status == SEC_I_CONTINUE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE
}
pub(super) fn needs_complete(status: SECURITY_STATUS) -> bool {
status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE
}
pub(super) fn get_max_token_size(package_name: &str) -> Result<u32, SECURITY_STATUS> {
let package_wide = to_wide_string(package_name);
let mut pkg_info: *mut SecPkgInfoW = ptr::null_mut();
let status = unsafe { QuerySecurityPackageInfoW(package_wide.as_ptr(), &mut pkg_info) };
if status != SEC_E_OK {
return Err(status);
}
let max_token = unsafe { (*pkg_info).cbMaxToken };
unsafe {
FreeContextBuffer(pkg_info as PVOID);
}
Ok(max_token)
}
pub(super) fn acquire_credentials(
package_name: &str,
) -> Result<(CredHandle, TimeStamp), SECURITY_STATUS> {
let package_wide = to_wide_string(package_name);
let mut cred_handle = CredHandle::default();
let mut expiry = TimeStamp::default();
let status = unsafe {
AcquireCredentialsHandleW(
ptr::null(), package_wide.as_ptr(),
SECPKG_CRED_OUTBOUND,
ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), &mut cred_handle,
&mut expiry,
)
};
if status != SEC_E_OK {
return Err(status);
}
Ok((cred_handle, expiry))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_wide_string() {
let wide = to_wide_string("Negotiate");
assert_eq!(
wide,
vec![
'N' as u16, 'e' as u16, 'g' as u16, 'o' as u16, 't' as u16, 'i' as u16, 'a' as u16,
't' as u16, 'e' as u16, 0
]
);
}
#[test]
fn test_is_success_status() {
assert!(is_success_status(SEC_E_OK));
assert!(is_success_status(SEC_I_CONTINUE_NEEDED));
assert!(!is_success_status(SEC_E_LOGON_DENIED));
}
#[test]
fn test_needs_continue() {
assert!(needs_continue(SEC_I_CONTINUE_NEEDED));
assert!(needs_continue(SEC_I_COMPLETE_AND_CONTINUE));
assert!(!needs_continue(SEC_E_OK));
}
#[test]
fn test_cred_handle_default() {
let handle = CredHandle::default();
assert!(!handle.is_valid());
}
#[test]
fn test_get_sspi_error_message() {
assert_eq!(get_sspi_error_message(SEC_E_OK), "Success");
assert_eq!(
get_sspi_error_message(SEC_E_TARGET_UNKNOWN),
"Target unknown (SPN not found)"
);
}
}