From: Arran Cudbard-Bell Date: Tue, 1 Jul 2025 11:57:08 +0000 (-0400) Subject: slapo-auditlog: Add olcAuditlogNonBlocking to avoid blocking when logging to named... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=HEAD;p=thirdparty%2Fopenldap.git slapo-auditlog: Add olcAuditlogNonBlocking to avoid blocking when logging to named pipes The default behaviour of fopen() when called on a named pipe which does not have any reader, is to block, until a reader opens the pipe. This blocks slapo-auditlog when it attempts to write output. Depending on how critical the audit log is, it may be preferable to discard audit log output and continue processing requests if there's no reader available. For clarity the call to fopen() is removed and replaced with open()/fdopen(), allowing us to specify O_* flags as opposed to using fopen() or open()/fdopen(). 0666 are the base permissions used by fopen() when files are created. --- diff --git a/doc/guide/admin/aspell.en.pws b/doc/guide/admin/aspell.en.pws index 67bd34635f..0dea8ace34 100644 --- a/doc/guide/admin/aspell.en.pws +++ b/doc/guide/admin/aspell.en.pws @@ -1172,6 +1172,7 @@ whitespace seeAlso monitorRuntimeConfig olcAuditlogFile +olcAuditlogNonBlocking namingContexts referralAttrDN moddn diff --git a/doc/man/man5/slapo-auditlog.5 b/doc/man/man5/slapo-auditlog.5 index be1522113c..6f6354c24b 100644 --- a/doc/man/man5/slapo-auditlog.5 +++ b/doc/man/man5/slapo-auditlog.5 @@ -19,19 +19,21 @@ For Add and Modify operations the identity comes from the modifiersName associated with the operation. This is usually the same as the requestor's identity, but may be set by other overlays to reflect other values. .SH CONFIGURATION -This -.B slapd.conf -option applies to the Audit Logging overlay. -It should appear after the -.B overlay -directive. +Both slapd.conf and back-config style configuration are supported. +.TP +.B overlay auditlog +This directive loads the auditlog overlay. .TP .B auditlog +.TP +.B olcAuditlogFile: Specify the fully qualified path for the log file. .TP -.B olcAuditlogFile -For use with -.B cn=config +.B auditlognonblocking {on|off} +.TP +.B olcAuditlogNonBlocking: {on|off} +Open in non-blocking mode. Useful if is a named pipe +and slapd should not block if no reader is available. .SH COMMENT FIELD INFORMATION The first field is the operation type. .br @@ -47,7 +49,7 @@ The sixth field is the connection number. A connection number of -1 indicates an internal slapd operation. .SH EXAMPLE The following LDIF could be used to add this overlay to -.B cn=config +.B cn=config (adjust to suit) .LP .RS diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c index 5047a8ef6b..3d808ba4e7 100644 --- a/servers/slapd/overlays/auditlog.c +++ b/servers/slapd/overlays/auditlog.c @@ -24,6 +24,7 @@ #ifdef SLAPD_OVER_AUDITLOG #include +#include #include #include @@ -35,6 +36,7 @@ typedef struct auditlog_data { ldap_pvt_thread_mutex_t ad_mutex; char *ad_logfile; + int ad_nonblocking; } auditlog_data; static ConfigTable auditlogcfg[] = { @@ -45,6 +47,13 @@ static ConfigTable auditlogcfg[] = { "DESC 'Filename for auditlogging' " "EQUALITY caseExactMatch " "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "auditlognonblocking", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(auditlog_data, ad_nonblocking), + "( OLcfgOvAt:15.2 NAME 'olcAuditlogNonBlocking' " + "DESC 'Set audit log file descriptor to non-blocking mode' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, { NULL, NULL, 0, 0, 0, ARG_IGNORED } }; @@ -53,7 +62,7 @@ static ConfigOCs auditlogocs[] = { "NAME 'olcAuditlogConfig' " "DESC 'Auditlog configuration' " "SUP olcOverlayConfig " - "MAY ( olcAuditlogFile ) )", + "MAY ( olcAuditlogFile $ olcAuditlogNonBlocking ) )", Cft_Overlay, auditlogcfg }, { NULL, 0, NULL } }; @@ -76,7 +85,7 @@ static int auditlog_response(Operation *op, SlapReply *rs) { struct berval *b, *who = NULL, peername; char *what, *whatm, *suffix; time_t stamp; - int i; + int i, flags, fd; if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; @@ -122,9 +131,20 @@ static int auditlog_response(Operation *op, SlapReply *rs) { peername = op->o_conn->c_peer_name; ldap_pvt_thread_mutex_lock(&ad->ad_mutex); - if((f = fopen(ad->ad_logfile, "a")) == NULL) { - ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); - return SLAP_CB_CONTINUE; + + /* Open file with optional non-blocking mode */ + flags = O_WRONLY | O_CREAT | O_APPEND; + if ( ad->ad_nonblocking ) { + flags |= O_NONBLOCK; + } + + fd = open(ad->ad_logfile, flags, 0666); + if ( fd == -1 ) goto done; + + f = fdopen(fd, "a"); + if ( f == NULL ) { + close(fd); + goto done; } stamp = slap_get_time(); @@ -180,6 +200,7 @@ static int auditlog_response(Operation *op, SlapReply *rs) { fprintf(f, "# end %s %ld\n\n", what, (long)stamp); fclose(f); +done: ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); return SLAP_CB_CONTINUE; } diff --git a/tests/scripts/test090-auditlog b/tests/scripts/test090-auditlog new file mode 100755 index 0000000000..d7a68b55fd --- /dev/null +++ b/tests/scripts/test090-auditlog @@ -0,0 +1,419 @@ +#! /bin/sh +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2025 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . + +echo "running defines.sh" +. $SRCDIR/scripts/defines.sh + +if test $AUDITLOG = auditlogno; then + echo "Auditlog overlay not available, test skipped" + exit 0 +fi + +mkdir -p $TESTDIR $DBDIR1 $TESTDIR/confdir + +$SLAPPASSWD -g -n >$CONFIGPWF +echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf + +echo "Starting slapd on TCP/IP port $PORT1..." +. $CONFFILTER $BACKEND < $NAKEDCONF > $CONF1 +$SLAPD -f $CONF1 -F $TESTDIR/confdir -h $URI1 -d $LVL > $LOG1 2>&1 & +PID=$! +if test $WAIT != 0 ; then + echo PID $PID + read foo +fi +KILLPIDS="$PID" + +sleep 1 +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \ + 'objectclass=*' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting 5 seconds for slapd to start..." + sleep 5 +done +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +cat /dev/null > $TESTOUT + +if [ "$AUDITLOG" = auditlogmod ]; then + echo "Inserting auditlog overlay on provider..." + $LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF < $TESTOUT 2>&1 +dn: cn=module,cn=config +objectClass: olcModuleList +cn: module +olcModulePath: ../servers/slapd/overlays +olcModuleLoad: auditlog.la +EOF + RC=$? + if test $RC != 0 ; then + echo "ldapadd failed for moduleLoad ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + fi +fi + +indexInclude="" mainInclude="" nullExclude="" +test $INDEXDB = indexdb || indexInclude="# " +test $MAINDB = maindb || mainInclude="# " +case $BACKEND in +null) nullExclude="# " ;; +esac + +AUDITLOGFILE="$TESTDIR/audit.log" + +echo "Running ldapadd to build slapd config database..." +$LDAPADD -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \ + >> $TESTOUT 2>&1 <> $TESTOUT 2>&1 << EOF +dn: $BASEDN +objectClass: organization +objectClass: dcObject +o: Example, Inc. +dc: example + +dn: ou=People,$BASEDN +objectClass: organizationalUnit +ou: People + +dn: cn=John Doe,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: John Doe +sn: Doe +givenName: John +mail: john.doe@example.com + +dn: cn=Jane Smith,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: Jane Smith +sn: Smith +givenName: Jane +mail: jane.smith@example.com + +EOF +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Testing ADD operations are logged..." +$LDAPADD -H $URI1 \ + -D "cn=Manager,$BASEDN" -w secret \ + >> $TESTOUT 2>&1 << EOF +dn: cn=Bob Jones,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: Bob Jones +sn: Jones +givenName: Bob +mail: bob.jones@example.com + +EOF +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Verifying ADD operation was logged..." +grep "# add " "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain ADD operations!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi +grep "cn=Bob Jones" "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain Bob Jones ADD entry!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi + +echo "Testing MODIFY operations are logged..." +$LDAPMODIFY -H $URI1 \ + -D "cn=Manager,$BASEDN" -w secret \ + >> $TESTOUT 2>&1 << EOF +dn: cn=John Doe,ou=People,$BASEDN +changetype: modify +replace: mail +mail: john.doe.new@example.com + +EOF +RC=$? +if test $RC != 0 ; then + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Verifying MODIFY operation was logged..." +grep "# modify " "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain MODIFY operations!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi +grep "john.doe.new@example.com" "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain the modified email address!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi + +echo "Testing MODRDN operations are logged..." +$LDAPMODIFY -H $URI1 \ + -D "cn=Manager,$BASEDN" -w secret \ + >> $TESTOUT 2>&1 << EOF +dn: cn=Jane Smith,ou=People,$BASEDN +changetype: modrdn +newrdn: cn=Jane Brown +deleteoldrdn: 1 + +EOF +RC=$? +if test $RC != 0 ; then + echo "ldapmodify modrdn failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Verifying MODRDN operation was logged..." +grep "# modrdn " "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain MODRDN operations!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi +grep "newrdn: cn=Jane Brown" "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain the new RDN!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi + +echo "Testing DELETE operations are logged..." +$LDAPMODIFY -H $URI1 \ + -D "cn=Manager,$BASEDN" -w secret \ + >> $TESTOUT 2>&1 << EOF +dn: cn=Bob Jones,ou=People,$BASEDN +changetype: delete + +EOF +RC=$? +if test $RC != 0 ; then + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo "Verifying DELETE operation was logged..." +grep "# delete " "$AUDITLOGFILE" > /dev/null +RC=$? +if test $RC != 0 ; then + echo "Auditlog does not contain DELETE operations!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi + +echo "Cleaning up audit log" +rm -f "$AUDITLOGFILE" + +if test "$OS_WINDOWS" == "yes"; then + echo "Skipping non-blocking tests on Windows..." +else + echo "Creating named pipe for blocking/non-blocking test" + PIPEFILE="$TESTDIR/audit.pipe" + mknod "$PIPEFILE" p + + echo "Testing non-blocking mode configuration..." + $LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \ + >> $TESTOUT 2>&1 <> $TESTOUT 2>&1 << EOF +dn: cn=NonBlocking Test,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: NonBlocking Test +sn: Test +givenName: NonBlocking +mail: nonblocking.test@example.com + +EOF + RC=$? + if test $RC != 0 ; then + echo "ldapadd failed with non-blocking mode and no reader ($RC)! This should have succeeded." + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + fi + + echo "Now testing non-blocking mode with pipe reader available..." + cat $PIPEFILE > $TESTDIR/pipe.log & + CATPID=$! + + echo "Adding entry with non-blocking mode and active reader..." + $LDAPADD -H $URI1 \ + -D "cn=Manager,$BASEDN" -w secret \ + >> $TESTOUT 2>&1 << EOF +dn: cn=Test User,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: Test User +sn: User +givenName: Test +mail: test.user@example.com + +EOF + RC=$? + if test $RC != 0 ; then + echo "ldapadd failed with non-blocking mode and reader ($RC)!" + kill $CATPID 2>/dev/null + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + fi + + echo "Verifying entry was logged to named pipe..." + sleep 1 + kill $CATPID 2>/dev/null + wait $CATPID 2>/dev/null + + if [ ! -f $TESTDIR/pipe.log ]; then + echo "Named pipe log file was not created!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 + fi + + grep "cn=Test User" $TESTDIR/pipe.log > /dev/null + RC=$? + if test $RC != 0 ; then + echo "Auditlog does not contain new entries with non-blocking mode and reader!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 + fi + + echo "Testing blocking mode and no pipe reader (should timeout)..." + $LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \ + >> $TESTOUT 2>&1 <> $TESTOUT 2>&1 << EOF +dn: cn=Blocking Test,ou=People,$BASEDN +objectClass: inetOrgPerson +cn: Blocking Test +sn: Test +givenName: Blocking +mail: blocking.test@example.com + +EOF + RC=$? + if test $RC != 124 ; then + echo "Blocking mode result: $BLOCKING_RC (should be timeout/error)" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC + fi + + # Briefly open the pipe, so that blocking writes are unblocked, and we can kill slapd cleanly + exec 3<>"$PIPEFILE" + + echo "Cleaning up named pipe and audit logs..." + rm -f $PIPEFILE $TESTDIR/pipe.log "$AUDITLOGFILE" +fi + +test $KILLSERVERS != no && kill -HUP $KILLPIDS + +echo ">>>>> Test succeeded" + +test $KILLSERVERS != no && wait + +exit 0