.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAPDN and operation"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry,1; ldap.responses.dn; content:"dc=example,dc=com";` sid:1;)
+
+ldap.responses.result_code
+--------------------------
+
+Suricata has a ``ldap.responses.result_code`` keyword that can be used in signatures to identify
+and filter network packets based on their LDAP result code.
+
+Syntax::
+
+ ldap.responses.result_code: code[,index];
+
+ldap.responses.result_code uses :ref:`unsigned 32-bit integer <rules-integer-keywords>`.
+
+This keyword maps to the following eve fields:
+
+ - ``ldap.responses[].bind_response.result_code``
+ - ``ldap.responses[].search_result_done.result_code``
+ - ``ldap.responses[].modify_response.result_code``
+ - ``ldap.responses[].add_response.result_code``
+ - ``ldap.responses[].del_response.result_code``
+ - ``ldap.responses[].mod_dn_response.result_code``
+ - ``ldap.responses[].compare_response.result_code``
+ - ``ldap.responses[].extended_response.result_code``
+
+.. table:: **Result code values for ldap.responses.result_code**
+
+ ========= ================================================
+ Code Name
+ ========= ================================================
+ 0 success
+ 1 operations_error
+ 2 protocol_error
+ 3 time_limit_exceeded
+ 4 size_limit_exceeded
+ 5 compare_false
+ 6 compare_true
+ 7 auth_method_not_supported
+ 8 stronger_auth_required
+ 10 referral
+ 11 admin_limit_exceeded
+ 12 unavailable_critical_extension
+ 13 confidentiality_required
+ 14 sasl_bind_in_progress
+ 16 no_such_attribute
+ 17 undefined_attribute_type
+ 18 inappropriate_matching
+ 19 constraint_violation
+ 20 attribute_or_value_exists
+ 21 invalid_attribute_syntax
+ 32 no_such_object
+ 33 alias_problem
+ 34 invalid_dns_syntax
+ 35 is_leaf
+ 36 alias_dereferencing_problem
+ 48 inappropriate_authentication
+ 49 invalid_credentials
+ 50 insufficient_access_rights
+ 51 busy
+ 52 unavailable
+ 53 unwilling_to_perform
+ 54 loop_detect
+ 60 sort_control_missing
+ 61 offset_range_error
+ 64 naming_violation
+ 65 object_class_violation
+ 66 not_allowed_on_non_leaf
+ 67 not_allowed_on_rdn
+ 68 entry_already_exists
+ 69 object_class_mods_prohibited
+ 70 results_too_large
+ 71 affects_multiple_dsas
+ 76 control_error
+ 80 other
+ 81 server_down
+ 82 local_error
+ 83 encoding_error
+ 84 decoding_error
+ 85 timeout
+ 86 auth_unknown
+ 87 filter_error
+ 88 user_canceled
+ 89 param_error
+ 90 no_memory
+ 91 connect_error
+ 92 not_supported
+ 93 control_not_found
+ 94 no_results_returned
+ 95 more_results_to_return
+ 96 client_loop
+ 97 referral_limit_exceeded
+ 100 invalid_response
+ 101 ambiguous_response
+ 112 tls_not_supported
+ 113 intermediate_response
+ 114 unknown_type
+ 118 canceled
+ 119 no_such_operation
+ 120 too_late
+ 121 cannot_cancel
+ 122 assertion_failed
+ 123 authorization_denied
+ 4096 e_sync_refresh_required
+ 16654 no_operation
+ ========= ================================================
+
+More information about LDAP result code values can be found here:
+https://ldap.com/ldap-result-code-reference/
+
+An LDAP request operation can receive multiple responses. By default, the ldap.responses.result_code
+keyword matches with any indices, but it is possible to specify a particular index for matching
+and also use flags such as ``all`` and ``any``.
+
+.. table:: **Index values for ldap.responses.result_code keyword**
+
+ ========= ================================================
+ Value Description
+ ========= ================================================
+ [default] Match with any index
+ all Match only if all indexes match
+ any Match with any index
+ 0>= Match specific index
+ 0< Match specific index with back to front indexing
+ ========= ================================================
+
+Examples
+^^^^^^^^
+
+Example of signatures that would alert if the packet has a ``success`` LDAP result code at any index:
+
+.. container:: example-rule
+
+ alert ldap any any -> any any (msg:"Test LDAP result code"; :example-rule-emphasis:`ldap.responses.result_code:0;` sid:1;)
+
+.. container:: example-rule
+
+ alert ldap any any -> any any (msg:"Test LDAP result code"; :example-rule-emphasis:`ldap.responses.result_code:success,any;` sid:1;)
+
+Example of a signature that would alert if the packet has an ``unavailable`` LDAP result code at index 1:
+
+.. container:: example-rule
+
+ alert ldap any any -> any any (msg:"Test LDAP result code at index 1"; :example-rule-emphasis:`ldap.responses.result_code:unavailable,1;` sid:1;)
+
+Example of a signature that would alert if all the responses have a ``success`` LDAP result code:
+
+.. container:: example-rule
+
+ alert ldap any any -> any any (msg:"Test all LDAP responses have success result code"; :example-rule-emphasis:`ldap.responses.result_code:success,all;` sid:1;)
+
+The keyword ldap.responses.result_code supports back to front indexing with negative numbers,
+this means that -1 will represent the last index, -2 the second to last index, and so on.
+This is an example of a signature that would alert if a ``success`` result code is found at the last index:
+
+.. container:: example-rule
+
+ alert ldap any any -> any any (msg:"Test LDAP success at last index"; :example-rule-emphasis:`ldap.responses.result_code:success,-1;` sid:1;)
DetectHelperMultiBufferMpmRegister, DetectSignatureSetAppProto, SCSigTableElmt,
SigMatchAppendSMToList, SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT,
};
-use crate::ldap::types::{LdapMessage, ProtocolOp, ProtocolOpCode};
+use crate::ldap::types::{LdapMessage, LdapResultCode, ProtocolOp, ProtocolOpCode};
use std::collections::VecDeque;
use std::ffi::CStr;
pub index: LdapIndex,
}
+struct DetectLdapRespResultData {
+ /// Ldap result code
+ pub du32: DetectUintData<u32>,
+ /// Index can be Any to match with any responses index,
+ /// All to match if all indices, or an i32 integer
+ /// Negative values represent back to front indexing.
+ pub index: LdapIndex,
+}
+
static mut G_LDAP_REQUEST_OPERATION_KW_ID: c_int = 0;
static mut G_LDAP_REQUEST_OPERATION_BUFFER_ID: c_int = 0;
static mut G_LDAP_RESPONSES_OPERATION_KW_ID: c_int = 0;
static mut G_LDAP_RESPONSES_COUNT_BUFFER_ID: c_int = 0;
static mut G_LDAP_REQUEST_DN_BUFFER_ID: c_int = 0;
static mut G_LDAP_RESPONSES_DN_BUFFER_ID: c_int = 0;
+static mut G_LDAP_RESPONSES_RESULT_CODE_KW_ID: c_int = 0;
+static mut G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID: c_int = 0;
unsafe extern "C" fn ldap_parse_protocol_req_op(
ustr: *const std::os::raw::c_char,
return true;
}
+fn aux_ldap_parse_resp_result_code(s: &str) -> Option<DetectLdapRespResultData> {
+ let parts: Vec<&str> = s.split(',').collect();
+ if parts.len() > 2 {
+ return None;
+ }
+
+ let index = parse_ldap_index(&parts)?;
+ let du32 = detect_parse_uint_enum::<u32, LdapResultCode>(parts[0])?;
+
+ Some(DetectLdapRespResultData { du32, index })
+}
+
+unsafe extern "C" fn ldap_parse_responses_result_code(
+ ustr: *const std::os::raw::c_char,
+) -> *mut DetectUintData<u32> {
+ let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
+ if let Ok(s) = ft_name.to_str() {
+ if let Some(ctx) = aux_ldap_parse_resp_result_code(s) {
+ let boxed = Box::new(ctx);
+ return Box::into_raw(boxed) as *mut _;
+ }
+ }
+ return std::ptr::null_mut();
+}
+
+unsafe extern "C" fn ldap_detect_responses_result_code_setup(
+ de: *mut c_void, s: *mut c_void, raw: *const libc::c_char,
+) -> c_int {
+ if DetectSignatureSetAppProto(s, ALPROTO_LDAP) != 0 {
+ return -1;
+ }
+ let ctx = ldap_parse_responses_result_code(raw) as *mut c_void;
+ if ctx.is_null() {
+ return -1;
+ }
+ if SigMatchAppendSMToList(
+ de,
+ s,
+ G_LDAP_RESPONSES_RESULT_CODE_KW_ID,
+ ctx,
+ G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID,
+ )
+ .is_null()
+ {
+ ldap_detect_responses_result_code_free(std::ptr::null_mut(), ctx);
+ return -1;
+ }
+ return 0;
+}
+
+fn get_ldap_result_code(response: &LdapMessage) -> Option<u32> {
+ return match &response.protocol_op {
+ ProtocolOp::BindResponse(resp) => Some(resp.result.result_code.0),
+ ProtocolOp::SearchResultDone(resp) => Some(resp.result_code.0),
+ ProtocolOp::ModifyResponse(resp) => Some(resp.result.result_code.0),
+ ProtocolOp::AddResponse(resp) => Some(resp.result_code.0),
+ ProtocolOp::DelResponse(resp) => Some(resp.result_code.0),
+ ProtocolOp::ModDnResponse(resp) => Some(resp.result_code.0),
+ ProtocolOp::CompareResponse(resp) => Some(resp.result_code.0),
+ ProtocolOp::ExtendedResponse(resp) => Some(resp.result.result_code.0),
+ _ => None,
+ };
+}
+
+unsafe extern "C" fn ldap_detect_responses_result_code_match(
+ _de: *mut c_void, _f: *mut c_void, _flags: u8, _state: *mut c_void, tx: *mut c_void,
+ _sig: *const c_void, ctx: *const c_void,
+) -> c_int {
+ let tx = cast_pointer!(tx, LdapTransaction);
+ let ctx = cast_pointer!(ctx, DetectLdapRespResultData);
+
+ return match_at_index::<LdapMessage, u32>(
+ &tx.responses,
+ &ctx.du32,
+ get_ldap_result_code,
+ |code, ctx_value| detect_match_uint(ctx_value, code) as c_int,
+ &ctx.index,
+ );
+}
+
+unsafe extern "C" fn ldap_detect_responses_result_code_free(_de: *mut c_void, ctx: *mut c_void) {
+ // Just unbox...
+ let ctx = cast_pointer!(ctx, DetectLdapRespResultData);
+ std::mem::drop(Box::from_raw(ctx));
+}
+
#[no_mangle]
pub unsafe extern "C" fn SCDetectLdapRegister() {
let kw = SCSigTableElmt {
false, //to server
ldap_detect_responses_dn_get_data,
);
+ let kw = SCSigTableElmt {
+ name: b"ldap.responses.result_code\0".as_ptr() as *const libc::c_char,
+ desc: b"match LDAPResult code\0".as_ptr() as *const libc::c_char,
+ url: b"/rules/ldap-keywords.html#ldap.responses.result_code\0".as_ptr()
+ as *const libc::c_char,
+ AppLayerTxMatch: Some(ldap_detect_responses_result_code_match),
+ Setup: ldap_detect_responses_result_code_setup,
+ Free: Some(ldap_detect_responses_result_code_free),
+ flags: 0,
+ };
+ G_LDAP_RESPONSES_RESULT_CODE_KW_ID = DetectHelperKeywordRegister(&kw);
+ G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID = DetectHelperBufferRegister(
+ b"ldap.responses.result_code\0".as_ptr() as *const libc::c_char,
+ ALPROTO_LDAP,
+ true, //to client
+ false, //to server
+ );
}
// written by Giuseppe Longo <giuseppe@glongo.it>
+use crate::detect::EnumString;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::ldap::filters::*;
use crate::ldap::ldap::LdapTransaction;
}
fn log_ldap_result(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
- js.set_string("result_code", &msg.result_code.to_string())?;
+ if let Some(rc) = LdapResultCode::from_u(msg.result_code.0) {
+ js.set_string("result_code", rc.to_str())?;
+ } else {
+ js.set_string("result_code", &format!("unknown-{}", msg.result_code.0))?;
+ }
js.set_string("matched_dn", &msg.matched_dn.0)?;
js.set_string("message", &msg.diagnostic_message.0)?;
Ok(())
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct ResultCode(pub u32);
-impl Display for ResultCode {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self.0 {
- 0 => write!(f, "success"),
- 1 => write!(f, "operations_error"),
- 2 => write!(f, "protocol_error"),
- 3 => write!(f, "time_limit_exceeded"),
- 4 => write!(f, "size_limit_exceeded"),
- 5 => write!(f, "compare_false"),
- 6 => write!(f, "compare_true"),
- 7 => write!(f, "auth_method_not_supported"),
- 8 => write!(f, "stronger_auth_required"),
- 10 => write!(f, "referral"),
- 11 => write!(f, "admin_limit_exceeded"),
- 12 => write!(f, "unavailable_critical_extension"),
- 13 => write!(f, "confidentiality_required"),
- 14 => write!(f, "sasl_bind_in_progress"),
- 16 => write!(f, "no_such_attribute"),
- 17 => write!(f, "undefined_attribute_type"),
- 18 => write!(f, "inappropriate_matching"),
- 19 => write!(f, "constraint_violation"),
- 20 => write!(f, "attribute_or_value_exists"),
- 21 => write!(f, "invalid_attribute_syntax"),
- 32 => write!(f, "no_such_object"),
- 33 => write!(f, "alias_problem"),
- 34 => write!(f, "invalid_dns_syntax"),
- 35 => write!(f, "is_leaf"),
- 36 => write!(f, "alias_dereferencing_problem"),
- 48 => write!(f, "inappropriate_authentication"),
- 49 => write!(f, "invalid_credentials"),
- 50 => write!(f, "insufficient_access_rights"),
- 51 => write!(f, "busy"),
- 52 => write!(f, "unavailable"),
- 53 => write!(f, "unwilling_to_perform"),
- 54 => write!(f, "loop_detect"),
- 60 => write!(f, "sort_control_missing"),
- 61 => write!(f, "offset_range_error"),
- 64 => write!(f, "naming_violation"),
- 65 => write!(f, "object_class_violation"),
- 66 => write!(f, "not_allowed_on_non_leaf"),
- 67 => write!(f, "not_allowed_on_rdn"),
- 68 => write!(f, "entry_already_exists"),
- 69 => write!(f, "object_class_mods_prohibited"),
- 70 => write!(f, "results_too_large"),
- 71 => write!(f, "affects_multiple_dsas"),
- 76 => write!(f, "control_error"),
- 80 => write!(f, "other"),
- 81 => write!(f, "server_down"),
- 82 => write!(f, "local_error"),
- 83 => write!(f, "encoding_error"),
- 84 => write!(f, "decoding_error"),
- 85 => write!(f, "timeout"),
- 86 => write!(f, "auth_unknown"),
- 87 => write!(f, "filter_error"),
- 88 => write!(f, "user_canceled"),
- 89 => write!(f, "param_error"),
- 90 => write!(f, "no_memory"),
- 91 => write!(f, "connect_error"),
- 92 => write!(f, "not_supported"),
- 93 => write!(f, "control_not_found"),
- 94 => write!(f, "no_results_returned"),
- 95 => write!(f, "more_results_to_return"),
- 96 => write!(f, "client_loop"),
- 97 => write!(f, "referral_limit_exceeded"),
- 100 => write!(f, "invalid_response"),
- 101 => write!(f, "ambiguous_response"),
- 112 => write!(f, "tls_not_supported"),
- 113 => write!(f, "intermediate_response"),
- 114 => write!(f, "unknown_type"),
- 118 => write!(f, "canceled"),
- 119 => write!(f, "no_such_operation"),
- 120 => write!(f, "too_late"),
- 121 => write!(f, "cannot_cancel"),
- 122 => write!(f, "assertion_failed"),
- 123 => write!(f, "authorization_denied"),
- 4096 => write!(f, "e_sync_refresh_required"),
- 16654 => write!(f, "no_operation"),
- _ => write!(f, "{}", self.0),
- }
- }
-}
-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct MessageID(pub u32);
pub response_value: Option<Vec<u8>>,
}
+#[derive(Clone, Debug, EnumStringU32)]
+#[repr(u32)]
+pub enum LdapResultCode {
+ Success = 0,
+ OperationsError = 1,
+ ProtocolError = 2,
+ TimeLimitExceeded = 3,
+ SizeLimitExceeded = 4,
+ CompareFalse = 5,
+ CompareTrue = 6,
+ AuthMethodNotSupported = 7,
+ StrongerAuthRequired = 8,
+ Referral = 10,
+ AdminLimitExceeded = 11,
+ UnavailableCriticalExtension = 12,
+ ConfidentialityRequired = 13,
+ SaslBindInProgress = 14,
+ NoSuchAttribute = 16,
+ UndefinedAttributeType = 17,
+ InappropriateMatching = 18,
+ ConstraintViolation = 19,
+ AttributeOrValueExists = 20,
+ InvalidAttributeSyntax = 21,
+ NoSuchObject = 32,
+ AliasProblem = 33,
+ InvalidDnsSyntax = 34,
+ IsLeaf = 35,
+ AliasDereferencingProblem = 36,
+ InappropriateAuthentication = 48,
+ InvalidCredentials = 49,
+ InsufficientAccessRights = 50,
+ Busy = 51,
+ Unavailable = 52,
+ UnwillingToPerform = 53,
+ LoopDetect = 54,
+ SortControlMissing = 60,
+ OffsetRangeError = 61,
+ NamingViolation = 64,
+ ObjectClassViolation = 65,
+ NotAllowedOnNonLeaf = 66,
+ NotAllowedOnRdn = 67,
+ EntryAlreadyExists = 68,
+ ObjectClassModsProhibited = 69,
+ ResultsTooLarge = 70,
+ AffectsMultipleDsas = 71,
+ ControlError = 76,
+ Other = 80,
+ ServerDown = 81,
+ LocalError = 82,
+ EncodingError = 83,
+ DecodingError = 84,
+ Timeout = 85,
+ AuthUnknown = 86,
+ FilterError = 87,
+ UserCanceled = 88,
+ ParamError = 89,
+ NoMemory = 90,
+ ConnectError = 91,
+ NotSupported = 92,
+ ControlNotFound = 93,
+ NoResultsReturned = 94,
+ MoreResultsToReturn = 95,
+ ClientLoop = 96,
+ ReferralLimitExceeded = 97,
+ InvalidResponse = 100,
+ AmbiguousResponse = 101,
+ TlsNotSupported = 112,
+ IntermediateResponse = 113,
+ UnknownType = 114,
+ Canceled = 118,
+ NoSuchOperation = 119,
+ TooLate = 120,
+ CannotCancel = 121,
+ AssertionFailed = 122,
+ AuthorizationDenied = 123,
+ ESyncRefreshRequired = 4096,
+ NoOperation = 16654,
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProtocolOp {
BindRequest(BindRequest),