]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
slapo-auditlog: Add olcAuditlogNonBlocking to avoid blocking when logging to named... master 780/head
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 1 Jul 2025 11:57:08 +0000 (07:57 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 3 Jul 2025 17:18:51 +0000 (11:18 -0600)
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.

doc/guide/admin/aspell.en.pws
doc/man/man5/slapo-auditlog.5
servers/slapd/overlays/auditlog.c
tests/scripts/test090-auditlog [new file with mode: 0755]

index 67bd34635f8139913817b34e385454e317fa36d4..0dea8ace345e4b56c09fa9e7576670ab8fc86e25 100644 (file)
@@ -1172,6 +1172,7 @@ whitespace
 seeAlso
 monitorRuntimeConfig
 olcAuditlogFile
+olcAuditlogNonBlocking
 namingContexts
 referralAttrDN
 moddn
index be1522113c292aac4610ee42bbebb00a04a7baa5..6f6354c24be20c004b0b671469809afc22c2c522 100644 (file)
@@ -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 <filename>
+.TP
+.B olcAuditlogFile: <filename>
 Specify the fully qualified path for the log file.
 .TP
-.B olcAuditlogFile <filename>
-For use with 
-.B cn=config
+.B auditlognonblocking {on|off}
+.TP
+.B olcAuditlogNonBlocking: {on|off}
+Open <filename> in non-blocking mode.  Useful if <filename> 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
index 5047a8ef6bbdf36fa146b7e34f2fe985538f2945..3d808ba4e79c1070adb44d660b014369e0902ff0 100644 (file)
@@ -24,6 +24,7 @@
 #ifdef SLAPD_OVER_AUDITLOG
 
 #include <stdio.h>
+#include <fcntl.h>
 
 #include <ac/string.h>
 #include <ac/ctype.h>
@@ -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 (executable)
index 0000000..d7a68b5
--- /dev/null
@@ -0,0 +1,419 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+
+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 <<EOF > $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 <<EOF
+dn: olcDatabase={1}$BACKEND,cn=config
+objectClass: olcDatabaseConfig
+${nullExclude}objectClass: olc${BACKEND}Config
+olcDatabase: {1}$BACKEND
+olcSuffix: $BASEDN
+olcRootDN: cn=Manager,$BASEDN
+olcRootPW:: c2VjcmV0
+olcMonitoring: TRUE
+${nullExclude}olcDbDirectory: $TESTDIR/db.1.a/
+${indexInclude}olcDbIndex: objectClass eq
+${indexInclude}olcDbIndex: cn pres,eq,sub
+${indexInclude}olcDbIndex: uid pres,eq,sub
+${indexInclude}olcDbIndex: sn pres,eq,sub
+${mainInclude}olcDbMode: 384
+
+dn: olcOverlay={0}auditlog,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcAuditlogConfig
+olcOverlay: {0}auditlog
+olcAuditlogFile: $AUDITLOGFILE
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapadd to build slapd database..."
+$LDAPADD -H $URI1 \
+       -D "cn=Manager,$BASEDN" -w secret \
+       >> $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 <<EOF
+dn: olcOverlay={0}auditlog,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcAuditlogFile
+olcAuditlogFile: $PIPEFILE
+-
+replace: olcAuditlogNonBlocking
+olcAuditlogNonBlocking: TRUE
+
+EOF
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapmodify failed for non-blocking config ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+
+       echo "Testing operation with non-blocking mode and no pipe reader (should succeed)..."
+       $LDAPADD -H $URI1 \
+               -D "cn=Manager,$BASEDN" -w secret \
+               >> $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 <<EOF
+dn: olcOverlay={0}auditlog,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcAuditlogFile
+olcAuditlogFile: $PIPEFILE
+-
+replace: olcAuditlogNonBlocking
+olcAuditlogNonBlocking: FALSE
+
+EOF
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapmodify failed for blocking config ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+
+       echo "Adding entry with blocking mode and no active reader..."
+       timeout 1 $LDAPADD -H $URI1 \
+               -D "cn=Manager,$BASEDN" -w secret \
+               >> $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