edf356
#!/bin/bash
edf356
# This script serves one purpose, to add a possibly missing attribute
edf356
# to a ppolicy schema in a dynamic configuration of OpenLDAP. This
edf356
# attribute was introduced in openldap-2.4.43 and slapd will not 
edf356
# start without it later on.
edf356
#
edf356
# The script tries to update in a directory given as first parameter,
edf356
# or in /etc/openldap/slapd.d implicitly.
edf356
#
edf356
# Author: Matus Honek <mhonek@redhat.com>
edf356
# Bugzilla: #1487857
edf356
edf356
function log {
edf356
    echo "Update dynamic configuration: " $@
edf356
    true
edf356
}
edf356
edf356
function iferr {
edf356
    if [ $? -ne 0 ]; then
edf356
	log "ERROR: " $@
edf356
	true
edf356
    else
edf356
	false
edf356
    fi
edf356
}
edf356
edf356
function update {
edf356
    set -u
edf356
    shopt -s extglob
edf356
edf356
    ORIGINAL="${1:-/etc/openldap/slapd.d}"
edf356
    ORIGINAL="${ORIGINAL%*(/)}"
edf356
edf356
    ### check if necessary
edf356
    grep -r "pwdMaxRecordedFail" "${ORIGINAL}/cn=config/cn=schema" >/dev/null
edf356
    [ $? -eq 0 ] && log "Schemas look up to date. Ok. Quitting." && return 0
edf356
edf356
    ### prep
edf356
    log "Prepare environment."
edf356
    
edf356
    TEMPDIR=$(mktemp -d)
edf356
    iferr "Could not create a temporary directory. Quitting." && return 1
edf356
    DBDIR="${TEMPDIR}/db"
edf356
    SUBDBDIR="${DBDIR}/cn=temporary"
edf356
edf356
    mkdir "${DBDIR}"
edf356
    iferr "Could not create temporary configuration directory. Quitting." && return 1
edf356
    cp -r --no-target-directory "${ORIGINAL}" "${SUBDBDIR}"
edf356
    iferr "Could not copy configuration. Quitting." && return 1
edf356
    
edf356
    pushd "$TEMPDIR" >/dev/null
edf356
edf356
    cat > temp.conf <
edf356
database ldif
edf356
suffix cn=temporary
edf356
directory db
edf356
access to * by * manage
edf356
EOF
edf356
    
edf356
    SOCKET="$(pwd)/socket"
edf356
    LISTENER="ldapi://${SOCKET//\//%2F}"
edf356
    CONN_PARAMS=("-Y" "EXTERNAL" "-H" "${LISTENER}")
edf356
    
edf356
    slapd -f temp.conf -h "$LISTENER" -d 0 >/dev/null 2>&1 &
edf356
    SLAPDPID="$!"
edf356
    sleep 2
edf356
edf356
    ldapadd ${CONN_PARAMS[@]} -d 0 >/dev/null 2>&1 <
edf356
dn: cn=temporary
edf356
objectClass: olcGlobal
edf356
cn: temporary
edf356
EOF
edf356
    iferr "Could not populate the temporary database. Quitting." && return 1
edf356
    
edf356
    ### update
edf356
    log "Update with new pwdMaxRecordedFailure attribute."
edf356
    FILTER="(&"
edf356
    FILTER+="(olcObjectClasses=*'pwdPolicy'*)"
edf356
    FILTER+="(!(olcObjectClasses=*'pwdPolicy'*'pwdMaxRecordedFailure'*))"
edf356
    FILTER+="(!(olcAttributeTypes=*'pwdMaxRecordedFailure'*))"
edf356
    FILTER+=")"
edf356
    RES=$(ldapsearch ${CONN_PARAMS[@]} \
edf356
		     -b cn=schema,cn=config,cn=temporary \
edf356
		     -LLL \
edf356
		     -o ldif-wrap=no \
edf356
		     "$FILTER" \
edf356
		     dn olcObjectClasses \
edf356
		     2>/dev/null \
edf356
	      | sed '/^$/d')
edf356
    DN=$(printf "$RES" | grep '^dn:')
edf356
    OC=$(printf "$RES" | grep "^olcObjectClasses:.*'pwdPolicy'")
edf356
    NEWOC="${OC//$ pwdSafeModify /$ pwdSafeModify $ pwdMaxRecordedFailure }"
edf356
edf356
    test $(echo "$DN" | wc -l) = 1
edf356
    iferr "Received more than one DN. Cannot continue. Quitting." && return 1
edf356
    test "$NEWOC" != "$OC"
edf356
    iferr "Updating pwdPolicy objectClass definition failed. Quitting." && return 1
edf356
edf356
    ldapmodify ${CONN_PARAMS[@]} -d 0 >/dev/null 2>&1 <
edf356
$DN
edf356
changetype: modify
edf356
add: olcAttributeTypes
edf356
olcAttributeTypes: ( 1.3.6.1.4.1.42.2.27.8.1.30 NAME 'pwdMaxRecordedFailur
edf356
 e' EQUALITY integerMatch ORDERING integerOrderingMatch  SYNTAX 1.3.6.1.4.1.
edf356
 1466.115.121.1.27 SINGLE-VALUE )
edf356
-
edf356
delete: olcObjectClasses
edf356
$OC
edf356
-
edf356
add: olcObjectClasses
edf356
$NEWOC
edf356
EOF
edf356
    iferr "Updating with new attribute failed. Quitting." && return 1
edf356
edf356
    popd >/dev/null
edf356
edf356
    ### apply
edf356
    log "Apply changes."
edf356
    cp -r --no-target-directory "$ORIGINAL" "$ORIGINAL~backup"
edf356
    iferr "Backing up old configuration failed. Quitting." && return 1
edf356
    cp -r --no-target-directory "$SUBDBDIR" "$ORIGINAL"
edf356
    iferr "Applying new configuration failed. Quitting." && return 1
edf356
    
edf356
    ### clean up
edf356
    log "Clean up."
edf356
    kill "$SLAPDPID"
edf356
    SLAPDPID=
edf356
    rm -rf "$TEMPDIR"
edf356
    TEMPDIR=
edf356
}
edf356
edf356
SLAPDPID=
edf356
TEMPDIR=
edf356
update "$1"
edf356
if [ $? -ne 0 ]; then
edf356
    log "Clean up."
edf356
    echo "$SLAPDPID"
edf356
    echo "$TEMPDIR"
edf356
    kill "$SLAPDPID"
edf356
    rm -rf "$TEMPDIR"
edf356
fi
edf356
log "Finished."