#!/bin/bash
##
## OpenLDAP: Backup slapd databases
## Copyright (c) 2012-2020 SATOH Fumiyasu @ OSS Technology Corp., Japan
##               <https://www.osstech.co.jp/>
##               <https://github.com/fumiyas/>
##
## License: GNU General Public License version 2 or later
##

set -u
set -o pipefail
umask 0027
renice 1 "$$" >/dev/null 2>&1 || :
ionice -c 3 -p "$$" >/dev/null 2>&1 || :

pdie()
{
  echo "$0: ERROR: $1" 1>&2
  exit ${2-1}
}

function ldifunwrap {
  sed \
    -n \
    '1 {h; $ !d}; $ {x; s/\n //g; p}; /^ / {H; d}; /^ /! {x; s/\n //g; p}' \
    "$@" \
  ;
}

## Check if slapd-config(5) (cn=config backend) is used or not
function slapd_config_backend_p {
  [[ -f $1/cn=config.ldif ]]
}

## ======================================================================

if [[ -z ${SLAPDBBACKUP_SETUIDGID-} && $(id -u) -eq 0 ]]; then
  export SLAPDBBACKUP_SETUIDGID=set
  user="${SLAPDBBACKUP_USER:-ldap}"
  if type setpriv >/dev/null 2>&1; then
    ## Old setpriv(1) can NOT take textual user and group names
    uid=$(id -u -- "$user")
    [[ -n $uid ]] || pdie "Failed to get the UID of a user: $user"
    gid=$(id -g -- "$user")
    [[ -n $gid ]] || pdie "Failed to get the primary GID of a user: $user"
    exec setpriv --reuid "$uid" --regid "$gid" --clear-groups -- "$0" "$@"
  elif type setuidgid >/dev/null 2>&1; then
    exec setuidgid -s "$user" "$0" "$@"
  else
    pdie 'No setpriv(1) or setuidgid(8) found'
  fi
  pdie "Failed to execute under the oridynary user privilege: $user" $?
fi

## Options
## ======================================================================

slapdbbacup_conf="${SLAPDBBACKUP_CONF:-/opt/osstech/etc/openldap/slapdbbackup.conf}"
slapcat_cmd="/opt/osstech/sbin/slapcat"
ldapsearch_cmd="/opt/osstech/bin/ldapsearch"
slapd_conf_file_default="/opt/osstech/etc/openldap/slapd.conf"
slapd_conf_dir_default="/opt/osstech/etc/openldap/slapd.d"
backup_dir="/opt/osstech/var/backup/ldap"
backup_prefix=""
backup_maxage="30"
network_timeout="3600"

if [[ -f $slapdbbacup_conf ]]; then
  set -e
  . "$slapdbbacup_conf"
  set +e
fi

slapcat_cmd="${SLAPDBBACKUP_SLAPCAT_COMMAND:-$slapcat_cmd}"
ldapsearch_cmd="${SLAPDBBACKUP_LDAPSEARCH_COMMAND:-$ldapsearch_cmd}"
slapd_conf="${SLAPDBBACKUP_SLAPD_CONF:-}"
backup_dir="${SLAPDBBACKUP_BACKUP_DIR:-$backup_dir}"
backup_maxage="${SLAPDBBACKUP_BACKUP_MAXAGE:-$backup_maxage}"

if [[ -z $slapd_conf ]]; then
  if slapd_config_backend_p "$slapd_conf_dir_default"; then
    slapd_conf="$slapd_conf_dir_default"
  else
    slapd_conf="$slapd_conf_file_default"
  fi
fi

## ======================================================================

{ 
  if slapd_config_backend_p "$slapd_conf"; then
    echo "config cn=config"
  fi

  {
    if slapd_config_backend_p "$slapd_conf"; then
      ldifunwrap "$slapd_conf"/cn=config/olcDatabase=*db.ldif \
      |sed -n \
	-e 's/^dn: .*}/database /p' \
	-e 's/^olcSuffix: /suffix /p' \
      ;
    else
      sed -e 's/"//g' -e 's/ *, */,/g' "$slapd_conf"
    fi
  } \
  |tr -d '\r' \
  |while read -r param value; do
    case "$param" in
    database)
      backend="$value"
      ;;
    suffix)
      if [[ $backend == [bhm]db || $backend == wt ]]; then
	echo "$backend $value"
      fi
      ;;
    esac
  done
} \
|while read -r backend suffix; do
  ldif="$backup_dir/$backup_prefix$suffix.ldif.gz"
  ldif_new="$ldif.$$.tmp"

  ## Test if the backup directory is writeable
  if ! touch "$ldif_new"; then
    pdie "Backup directory is not writeable"
  fi

  (
    ## WiredTiger does not support slapcat online
    if [[ $backend == wt ]] &&
       "$ldapsearch_cmd" -H ldapi:/// -QY external -b '' -s base >&/dev/null
    then
      "$ldapsearch_cmd" \
        -H ldapi:/// \
	-QY external \
	-b "$suffix" \
	-o nettimeout="$network_timeout" \
	-l none \
	-z none \
	-LLL \
	'*' + \
      |sed \
        -e '/^entryDN:/d' \
        -e '/^subschemaSubentry:/d' \
        -e '/^hasSubordinates:/d' \
      ;
    else
      "$slapcat_cmd" -b "$suffix"
    fi
  ) |gzip -1 >"$ldif_new"
  backup_rc="$?"
  if [[ $backup_rc -ne 0 ]]; then
    rm -f "$ldif_new"
    pdie "Backup command failed: $suffix: Exit status $backup_rc"
  fi

  if [[ -f "$ldif" ]]; then
    backup_age="$backup_maxage"
    if [[ -f "$ldif.$backup_age" ]]; then
      rm -f "$ldif.$backup_age" || exit $?
    fi
    while [[ "$backup_age" -gt 0 ]]; do
      backup_age_prev=$((backup_age - 1))
      if [[ -f "$ldif.$backup_age_prev" ]]; then
	mv "$ldif.$backup_age_prev" "$ldif.$backup_age" || exit $?
      fi
      backup_age="$backup_age_prev"
    done

    mv "$ldif" "$ldif.1" || exit $?
  fi

  mv "$ldif_new" "$ldif" || exit $?
done

exit 0
