use ldap3_proto::proto::{LdapBindCred, LdapBindRequest, LdapBindResponse, LdapOp, LdapResult}; use ldap3_proto::{LdapMsg, LdapResultCode}; use crate::db::error::BoxedError; use crate::db::{Database, DatabaseInterface}; use crate::ldap::{ Dn, InvalidDnError, LdapReturnError, LdapStream, LdapStreamError, NotUserDnError, }; #[derive(Debug)] pub enum BindError { Db(BoxedError), InvalidCredentials, InvalidDn(InvalidDnError), NotUserDn(NotUserDnError), UnsupportedSASL, } impl BindError { pub async fn error_message( &self, stream: &mut LdapStream, msgid: i32, ) -> Result<(), LdapStreamError> { tracing::debug!(error=?self, "Bind failed"); let resp_msg = LdapMsg { msgid, op: LdapOp::BindResponse(LdapBindResponse { res: LdapResult { code: self.code(), matcheddn: String::new(), message: self.message(), referral: vec![], }, saslcreds: None, }), ctrl: vec![], }; stream.send(resp_msg).await?; Ok(()) } } impl From for BindError { fn from(e: InvalidDnError) -> Self { Self::InvalidDn(e) } } impl LdapReturnError for BindError { fn code(&self) -> LdapResultCode { match self { Self::Db(_e) => LdapResultCode::Unavailable, Self::InvalidCredentials => LdapResultCode::InvalidCredentials, Self::InvalidDn(e) => e.code(), Self::NotUserDn(e) => e.code(), Self::UnsupportedSASL => LdapResultCode::OperationsError, } } fn message(&self) -> String { match self { Self::Db(e) => format!("Database error: {e}"), Self::InvalidCredentials => "Wrong username or password".to_string(), Self::InvalidDn(e) => e.message(), Self::NotUserDn(e) => e.message(), Self::UnsupportedSASL => "SASL login is not supported".to_string(), } } } pub async fn bind_success(stream: &mut LdapStream, msgid: i32) -> Result<(), LdapStreamError> { let resp_msg = LdapMsg { msgid, op: LdapOp::BindResponse(LdapBindResponse { res: LdapResult { code: LdapResultCode::Success, matcheddn: String::new(), message: String::new(), referral: vec![], }, saslcreds: None, }), ctrl: vec![], }; stream.send(resp_msg).await?; Ok(()) } /// Tries to bind the user. /// /// On success, returns `Ok(Some(bound_dn))`. `Ok(None)` means credentials failed, /// either because the account does not exist, or the password is wrong. pub async fn op_bind( stream: &mut LdapStream, db: &Database, req: LdapBindRequest, msgid: i32, ) -> Result, LdapStreamError> { let dn = match Dn::from_dn_str(&req.dn) { Ok(dn) => dn, Err(e) => { BindError::InvalidDn(e).error_message(stream, msgid).await?; return Ok(None); } }; let LdapBindCred::Simple(password) = req.cred else { BindError::UnsupportedSASL .error_message(stream, msgid) .await?; return Ok(None); }; let user_ref = match dn.to_user_ref() { Ok(user_ref) => user_ref, Err(e) => { BindError::NotUserDn(e).error_message(stream, msgid).await?; return Ok(None); } }; let success = match db.check_password(&user_ref, &password).await { Ok(success) => success, Err(e) => { // tracing::error!(error = &*e as &dyn std::error::Error, "Database failure"); tracing::error!(error = e, "Database failure"); BindError::Db(e).error_message(stream, msgid).await?; return Ok(None); } }; if success { bind_success(stream, msgid).await?; Ok(Some(dn)) } else { BindError::InvalidCredentials .error_message(stream, msgid) .await?; Ok(None) } }