ldap: Use scalar values in logs where appropriate

Skimming through the RFC, the previous approach of having containers for most
fields seems unfounded for normal protocol operation. The new weirds could just
as well be considered protocol violations. Outside of duplicated or missed data
they just shouldn't happen for well-behaved client/server behavior.
Additionally, with non-conformant traffic it would be trivial to cause
unbounded state growth and immense log record sizes.

Unfortunately, things have become a bit clunky now.

Closes #3504
This commit is contained in:
Arne Welzel 2023-12-14 14:02:30 +01:00
parent 46d0287b49
commit 242db4981d
13 changed files with 189 additions and 119 deletions

25
NEWS
View file

@ -23,6 +23,31 @@ Breaking Changes
SOURCES ...
)
- Certain ``ldap.log`` and ``ldap_search.log`` fields have been renamed from
plural to singular and their types changed to scalars. This maps better onto
the expected request-response protocol used between client and server. Additionally,
it removes the burden of working with non-scalar columns from downstream systems.
Specifically, for ``ldap.log``:
* ``arguments: vector of string`` is now ``argument: string``
* ``diagnostic_messages: vector of string`` is now ``diagnostic_message: string``
* ``objects: vector of string`` is now ``object: string``
* ``opcodes: set[string]`` is now ``opcode: string``
* ``results: set[string]`` is now ``result: string``
For ``ldap_search.log``, the following fields were changed:
* ``base_objects: vector of string`` is now ``base_object: string``
* ``derefs: set[string]`` is now ``deref_aliases: string``
* ``diagnostic_messages: vector of string`` is now ``diagnostic_message: string``
* ``results: set[string]`` is now ``result: string``
* ``scopes: set[string]`` is now ``scope: string``
In the unlikely scenario that a request-response pair with the same message
identifier is observed, containing different values for certain fields, new
weirds are raised and will appear in ``weird.log``, including the old and new
values as well as the LDAP message identifier. The value within the LDAP logs
will be the most recently observed one.
New Functionality
-----------------

View file

@ -1,5 +1,6 @@
# Copyright (c) 2021 by the Zeek Project. See LICENSE for details.
@load base/frameworks/reporter
@load base/protocols/conn/removal-hooks
@load ./consts
@ -49,20 +50,20 @@ export {
# LDAP version
version: int &log &optional;
# normalized operations (e.g., bind_request and bind_response to "bind")
opcodes: set[string] &log &optional;
# Normalized operation (e.g., bind_request and bind_response to "bind")
opcode: string &log &optional;
# Result code(s)
results: set[string] &log &optional;
# Result code
result: string &log &optional;
# result diagnostic message(s)
diagnostic_messages: vector of string &log &optional;
# Result diagnostic message
diagnostic_message: string &log &optional;
# object(s)
objects: vector of string &log &optional;
# Object
object: string &log &optional;
# argument(s)
arguments: vector of string &log &optional;
# Argument
argument: string &log &optional;
};
#############################################################################
@ -82,25 +83,25 @@ export {
message_id: int &log &optional;
# sets of search scope and deref alias
scopes: set[string] &log &optional;
derefs: set[string] &log &optional;
scope: string &log &optional;
deref_aliases: string &log &optional;
# base search objects
base_objects: vector of string &log &optional;
# Base search objects
base_object: string &log &optional;
# number of results returned
# Number of results returned
result_count: count &log &optional;
# Result code (s)
results: set[string] &log &optional;
# Result code of search operation
result: string &log &optional;
# result diagnostic message(s)
diagnostic_messages: vector of string &log &optional;
# Result diagnostic message
diagnostic_message: string &log &optional;
# a string representation of the search filter used in the query
# A string representation of the search filter used in the query
filter: string &log &optional;
# a list of attributes that were returned in the search
# A list of attributes that were returned in the search
attributes: vector of string &log &optional;
};
@ -189,72 +190,93 @@ event LDAP::message(c: connection,
if (opcode == LDAP::ProtocolOpcode_SEARCH_RESULT_DONE) {
set_session(c, message_id, opcode);
local searches = c$ldap$searches[message_id];
local sm = c$ldap$searches[message_id];
if ( result != LDAP::ResultCode_Undef ) {
if ( ! searches?$results )
searches$results = set();
add searches$results[RESULT_CODES[result]];
local sresult_str = RESULT_CODES[result];
if ( sm?$result && sm$result != sresult_str ) {
Reporter::conn_weird("LDAP_search_result_change", c,
fmt("%s: %s -> %s", message_id, sm$result, sresult_str), "LDAP");
}
sm$result = sresult_str;
}
if ( diagnostic_message != "" ) {
if ( ! searches?$diagnostic_messages )
searches$diagnostic_messages = vector();
searches$diagnostic_messages += diagnostic_message;
if ( ! sm?$diagnostic_message && sm$diagnostic_message != diagnostic_message ) {
Reporter::conn_weird("LDAP_search_diagnostic_message_change", c,
fmt("%s: %s -> %s", message_id, sm$diagnostic_message, diagnostic_message), "LDAP");
}
sm$diagnostic_message = diagnostic_message;
}
Log::write(LDAP::LDAP_SEARCH_LOG, searches);
Log::write(LDAP::LDAP_SEARCH_LOG, sm);
delete c$ldap$searches[message_id];
} else if (opcode !in OPCODES_SEARCH) {
} else if (opcode !in OPCODES_SEARCH) { # search is handled via LDAP::search_request()
set_session(c, message_id, opcode);
local messages = c$ldap$messages[message_id];
local m = c$ldap$messages[message_id];
if ( ! messages?$opcodes )
messages$opcodes = set();
add messages$opcodes[PROTOCOL_OPCODES[opcode]];
local opcode_str = PROTOCOL_OPCODES[opcode];
# bind request is explicitly handled via LDAP::bind_request() and
# can assume we have a more specific m$opcode set.
if ( opcode_str != "bind" ) {
if ( m?$opcode && opcode_str != m$opcode ) {
Reporter::conn_weird("LDAP_message_opcode_change", c,
fmt("%s: %s -> %s", message_id, m$opcode, opcode_str), "LDAP");
}
m$opcode = opcode_str;
}
if ( result != LDAP::ResultCode_Undef ) {
if ( ! messages?$results )
messages$results = set();
add messages$results[RESULT_CODES[result]];
local result_str = RESULT_CODES[result];
if ( m?$result && m$result != result_str ) {
Reporter::conn_weird("LDAP_message_result_change", c,
fmt("%s: %s -> %s", message_id, m$result, result_str), "LDAP");
}
m$result = result_str;
}
if ( diagnostic_message != "" ) {
if ( ! messages?$diagnostic_messages )
messages$diagnostic_messages = vector();
messages$diagnostic_messages += diagnostic_message;
if ( m?$diagnostic_message && diagnostic_message != m$diagnostic_message ) {
Reporter::conn_weird("LDAP_message_diagnostic_message_change", c,
fmt("%s: %s -> %s", message_id, m$diagnostic_message, diagnostic_message), "LDAP");
}
m$diagnostic_message = diagnostic_message;
}
if ( object != "" ) {
if ( ! messages?$objects )
messages$objects = vector();
messages$objects += object;
if ( m?$object && m$object != object ) {
Reporter::conn_weird("LDAP_message_object_change", c,
fmt("%s: %s -> %s", message_id, m$object, object), "LDAP");
}
m$object = object;
}
if ( argument != "" ) {
if ( ! messages?$arguments )
messages$arguments = vector();
if ("bind simple" in messages$opcodes && !default_capture_password)
messages$arguments += "REDACTED";
else
messages$arguments += argument;
if ( m$opcode == BIND_SIMPLE && ! default_capture_password )
argument = "REDACTED";
if ( m?$argument && m$argument != argument ) {
Reporter::conn_weird("LDAP_message_argument_change", c,
fmt("%s: %s -> %s", message_id, m$argument, argument), "LDAP");
}
m$argument = argument;
}
if (opcode in OPCODES_FINISHED) {
if ((BIND_SIMPLE in messages$opcodes) ||
(BIND_SASL in messages$opcodes)) {
# don't have both "bind" and "bind <method>" in the operations list
delete messages$opcodes[PROTOCOL_OPCODES[LDAP::ProtocolOpcode_BIND_REQUEST]];
}
Log::write(LDAP::LDAP_LOG, messages);
Log::write(LDAP::LDAP_LOG, m);
delete c$ldap$messages[message_id];
}
}
}
#############################################################################
@ -271,27 +293,50 @@ event LDAP::search_request(c: connection,
set_session(c, message_id, LDAP::ProtocolOpcode_SEARCH_REQUEST);
local sm = c$ldap$searches[message_id];
if ( scope != LDAP::SearchScope_Undef ) {
if ( ! c$ldap$searches[message_id]?$scopes )
c$ldap$searches[message_id]$scopes = set();
add c$ldap$searches[message_id]$scopes[SEARCH_SCOPES[scope]];
local scope_str = SEARCH_SCOPES[scope];
if ( sm?$scope && sm$scope != scope_str ) {
Reporter::conn_weird("LDAP_search_scope_change", c,
fmt("%s: %s -> %s", message_id, sm$scope, scope_str), "LDAP");
}
sm$scope = scope_str;
}
if ( deref != LDAP::SearchDerefAlias_Undef ) {
if ( ! c$ldap$searches[message_id]?$derefs )
c$ldap$searches[message_id]$derefs = set();
add c$ldap$searches[message_id]$derefs[SEARCH_DEREF_ALIASES[deref]];
local deref_aliases_str = SEARCH_DEREF_ALIASES[deref];
if ( sm?$deref_aliases && sm$deref_aliases != deref_aliases_str ) {
Reporter::conn_weird("LDAP_search_deref_aliases_change", c,
fmt("%s: %s -> %s", message_id, sm$deref_aliases, deref_aliases_str), "LDAP");
}
sm$deref_aliases = deref_aliases_str;
}
if ( base_object != "" ) {
if ( ! c$ldap$searches[message_id]?$base_objects )
c$ldap$searches[message_id]$base_objects = vector();
c$ldap$searches[message_id]$base_objects += base_object;
if ( sm?$base_object && sm$base_object != base_object ) {
Reporter::conn_weird("LDAP_search_base_object_change", c,
fmt("%s: %s -> %s", message_id, sm$base_object, base_object), "LDAP");
}
sm$base_object = base_object;
}
c$ldap$searches[message_id]$filter = filter;
if ( sm?$filter && sm$filter != filter )
Reporter::conn_weird("LDAP_search_filter_change", c,
fmt("%s: %s -> %s", message_id, sm$filter, filter), "LDAP");
sm$filter = filter;
if ( default_log_search_attributes ) {
c$ldap$searches[message_id]$attributes = attributes;
if ( sm?$attributes && cat(sm$attributes) != cat(attributes) ) {
Reporter::conn_weird("LDAP_search_attributes_change", c,
fmt("%s: %s -> %s", message_id, sm$attributes, attributes), "LDAP");
}
sm$attributes = attributes;
}
}
@ -314,16 +359,23 @@ event LDAP::bind_request(c: connection,
authInfo: string) {
set_session(c, message_id, LDAP::ProtocolOpcode_BIND_REQUEST);
if ( ! c$ldap$messages[message_id]?$version )
c$ldap$messages[message_id]$version = version;
local m = c$ldap$messages[message_id];
if ( ! c$ldap$messages[message_id]?$opcodes )
c$ldap$messages[message_id]$opcodes = set();
if ( ! m?$version )
m$version = version;
# Getting herre, we don't expect the LDAP opcode to be set at all
# and it'll be overwritten below.
if ( m?$opcode )
Reporter::conn_weird("LDAP_bind_opcode_already_set", c, m$opcode, "LDAP");
if (authType == LDAP::BindAuthType_BIND_AUTH_SIMPLE) {
add c$ldap$messages[message_id]$opcodes[BIND_SIMPLE];
m$opcode = BIND_SIMPLE;
} else if (authType == LDAP::BindAuthType_BIND_AUTH_SASL) {
add c$ldap$messages[message_id]$opcodes[BIND_SASL];
m$opcode = BIND_SASL;
} else {
Reporter::conn_weird("LDAP_unknown_auth_type", c, cat(authType), "LDAP");
m$opcode = cat(authType);
}
}
@ -333,15 +385,8 @@ hook finalize_ldap(c: connection) {
if ( c$ldap?$messages && (|c$ldap$messages| > 0) ) {
for ( [mid], m in c$ldap$messages ) {
if (mid > 0) {
if ((BIND_SIMPLE in m$opcodes) || (BIND_SASL in m$opcodes)) {
# don't have both "bind" and "bind <method>" in the operations list
delete m$opcodes[PROTOCOL_OPCODES[LDAP::ProtocolOpcode_BIND_REQUEST]];
}
if (mid > 0)
Log::write(LDAP::LDAP_LOG, m);
}
}
delete c$ldap$messages;
}

View file

@ -365,14 +365,14 @@ connection {
LDAP::State {
* messages: table[int] of record LDAP::MessageInfo, log=F, optional=T
LDAP::MessageInfo {
* arguments: vector of string, log=T, optional=T
* diagnostic_messages: vector of string, log=T, optional=T
* argument: string, log=T, optional=T
* diagnostic_message: string, log=T, optional=T
* id: record conn_id, log=T, optional=F
conn_id { ... }
* message_id: int, log=T, optional=T
* objects: vector of string, log=T, optional=T
* opcodes: set[string], log=T, optional=T
* results: set[string], log=T, optional=T
* object: string, log=T, optional=T
* opcode: string, log=T, optional=T
* result: string, log=T, optional=T
* ts: time, log=T, optional=F
* uid: string, log=T, optional=F
* version: int, log=T, optional=T
@ -380,16 +380,16 @@ connection {
* searches: table[int] of record LDAP::SearchInfo, log=F, optional=T
LDAP::SearchInfo {
* attributes: vector of string, log=T, optional=T
* base_objects: vector of string, log=T, optional=T
* derefs: set[string], log=T, optional=T
* diagnostic_messages: vector of string, log=T, optional=T
* base_object: string, log=T, optional=T
* deref_aliases: string, log=T, optional=T
* diagnostic_message: string, log=T, optional=T
* filter: string, log=T, optional=T
* id: record conn_id, log=T, optional=F
conn_id { ... }
* message_id: int, log=T, optional=T
* result: string, log=T, optional=T
* result_count: count, log=T, optional=T
* results: set[string], log=T, optional=T
* scopes: set[string], log=T, optional=T
* scope: string, log=T, optional=T
* ts: time, log=T, optional=F
* uid: string, log=T, optional=F
}

View file

@ -5,8 +5,8 @@
#unset_field -
#path ldap
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments
#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string]
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcode result diagnostic_message object argument
#types time string addr port addr port int int string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx,OU=Users,OU=Accounts,DC=xx,DC=xxx,DC=xxxxx,DC=net REDACTED
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap_search
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes
#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) sAMAccountName
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scope deref_aliases base_object result_count result diagnostic_message filter attributes
#types time string addr port addr port int string string string count string string string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx,DC=xxx,DC=xxxxx,DC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) sAMAccountName
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,8 +5,8 @@
#unset_field -
#path ldap
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments
#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string]
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcode result diagnostic_message object argument
#types time string addr port addr port int int string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx,OU=Users,OU=Accounts,DC=xx,DC=xxx,DC=xxxxx,DC=net REDACTED
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap_search
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes
#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) -
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scope deref_aliases base_object result_count result diagnostic_message filter attributes
#types time string addr port addr port int string string string count string string string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx,DC=xxx,DC=xxxxx,DC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,8 +5,8 @@
#unset_field -
#path ldap
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments
#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string]
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcode result diagnostic_message object argument
#types time string addr port addr port int int string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 3 3 bind simple success - CN=xxxxxxxx,OU=Users,OU=Accounts,DC=xx,DC=xxx,DC=xxxxx,DC=net REDACTED
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap_search
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes
#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) -
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scope deref_aliases base_object result_count result diagnostic_message filter attributes
#types time string addr port addr port int string string string count string string string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 2 tree always DC=xx,DC=xxx,DC=xxxxx,DC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments
#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string]
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcode result diagnostic_message object argument
#types time string addr port addr port int int string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.31.1.104 3116 172.31.1.101 389 215 3 bind SASL success - - GSS-SPNEGO
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap_search
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes
#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string]
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scope deref_aliases base_object result_count result diagnostic_message filter attributes
#types time string addr port addr port int string string string count string string string vector[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.31.1.104 3116 172.31.1.101 389 213 base never - 1 success - (objectclass=*) -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -5,7 +5,7 @@
#unset_field -
#path ldap_search
#open XXXX-XX-XX-XX-XX-XX
#fields uid filter base_objects
#types string string vector[string]
CHhAvVGS1DHFjwGM9 (departmentNumber:2.16.840.1.113730.3.3.2.46.1:=>=N4709) DC=matrix\x2cDC=local
#fields uid filter base_object
#types string string string
CHhAvVGS1DHFjwGM9 (departmentNumber:2.16.840.1.113730.3.3.2.46.1:=>=N4709) DC=matrix,DC=local
#close XXXX-XX-XX-XX-XX-XX

View file

@ -4,7 +4,7 @@
#
# @TEST-REQUIRES: have-spicy
# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/issue-32.pcapng %INPUT
# @TEST-EXEC: cat ldap_search.log | zeek-cut -C uid filter base_objects > ldap_search.log2 && mv ldap_search.log2 ldap_search.log
# @TEST-EXEC: cat ldap_search.log | zeek-cut -C uid filter base_object > ldap_search.log2 && mv ldap_search.log2 ldap_search.log
# @TEST-EXEC: btest-diff ldap_search.log
#
# @TEST-DOC: Test LDAP analyzer with small trace.