#! /bin/sh set -e # Load debconf . /usr/share/debconf/confmodule # This will be replaced with debian/slapd.scripts-common which includes # various helper functions and $OLD_VERSION and $SLAPD_CONF # -*- sh -*- # This file can be included with #SCRIPTSCOMMON# # ===== Dumping and reloading using LDIF files ========================= {{{ # # If incompatible changes are done to the database underlying a LDAP # directory we need to dump the contents and reload the data into a newly # created database after the new server was installed. The following # functions deal with this functionality. # ----- Configuration of this component -------------------------------- {{{ # # Dumping the database can have negative effects on the system we are # running on. If there is a lot of data dumping it might fill a partition # for example. Therefore we must give the user exact control over what we # are doing. database_dumping_enabled() { # {{{ # Check if the user has enabled database dumping for the current situation. # Return success if yes. # Usage: if database_dumping_enabled; then ... fi # If the package is being removed, dump unconditionally as we # don't know whether the next version will require reload. [ "$MODE" = remove ] && return 0 db_get slapd/dump_database case "$RET" in always) ;; "when needed") database_format_changed || return 1 ;; never) return 1 ;; *) echo >&2 "Unknown value for slapd/dump_database: $RET" echo >&2 "Please report!" exit 1 ;; esac } # }}} database_format_changed() { # {{{ # Check if the database format has changed since the old installed version # Return success if yes. # Usage: if database_format_changed; then if dpkg --compare-versions "$OLD_VERSION" lt-nl 2.5; then return 0 else return 1 fi } # }}} database_dumping_destdir() { # {{{ # Figure out the directory we are dumping the database to and create it # if it does not exist. # Usage: destdir=`database_dumping_destdir` local dir db_get slapd/dump_database_destdir dir=`echo "$RET"|sed -e "s/VERSION/$OLD_VERSION/"` mkdir -p -m 700 "$dir" echo $dir } # }}} create_new_user() { # {{{ if [ -z "`getent group openldap`" ]; then addgroup --quiet --system openldap fi if [ -z "`getent passwd openldap`" ]; then echo -n " Creating new user openldap... " >&2 adduser --quiet --system --home /var/lib/ldap --shell /bin/false \ --ingroup openldap --disabled-password --disabled-login \ --gecos "OpenLDAP Server Account" openldap echo "done." >&2 fi } # }}} create_ldap_directories() { # {{{ if [ ! -d /var/lib/ldap ]; then mkdir -m 0700 /var/lib/ldap fi if [ ! -d /var/run/slapd ]; then mkdir -m 0755 /var/run/slapd fi update_permissions /var/lib/ldap update_permissions /var/run/slapd } # }}} update_permissions() { # {{{ local dir dir="$1" if [ -d "$dir" ]; then [ -z "$SLAPD_USER" ] || chown -R -H "$SLAPD_USER" "$dir" [ -z "$SLAPD_GROUP" ] || chgrp -R -H "$SLAPD_GROUP" "$dir" fi } # }}} update_databases_permissions() { # {{{ get_suffix | while read -r suffix; do dbdir=`get_directory "$suffix"` update_permissions "$dbdir" done } # }}} # }}} # ----- Dumping and loading the data ------------------------------------ {{{ dump_config() { # {{{ # Dump the cn=config database to the backup directory. # This is not the same as backup_config_once, which copies the slapd.d # directory verbatim. local dir [ -d "$SLAPD_CONF" ] || return 0 dir="$(database_dumping_destdir)" echo "Saving current slapd configuration to $dir..." >&2 slapcat -F "$SLAPD_CONF" -n0 -l "$dir/cn=config.ldif" } # }}} dump_databases() { # {{{ # If the user wants us to dump the databases they are dumped to the # configured directory. local db suffix file dir failed slapcat_opts database_dumping_enabled || return 0 dir=`database_dumping_destdir` echo >&2 " Dumping to $dir: " (get_suffix | while read -r suffix; do dbdir=`get_directory "$suffix"` if [ -n "$dbdir" ]; then file="$dir/$suffix.ldif" printf ' - directory %s... ' "$suffix" >&2 # Need to support slapd.d migration from preinst if [ -f "${SLAPD_CONF}" ]; then slapcat_opts="-g -f ${SLAPD_CONF}" else slapcat_opts="-g -F ${SLAPD_CONF}" fi slapcat ${slapcat_opts} -b "$suffix" > "$file" || failed=1 if [ "$failed" ]; then rm -f "$file" echo "failed." >&2 db_subst slapd/upgrade_slapcat_failure location "$dir" <&5 db_input critical slapd/upgrade_slapcat_failure <&5 || true db_go <&5 || true exit 1 fi echo "done." >&2 fi done) 5<&0 &2 " Loading from $dir: " # restore by increasing suffix length due to possibly glued databases get_suffix | awk '{ print length, $0 }' | sort -n | cut -d ' ' -f 2- \ | while read -r suffix; do dbdir=`get_directory "$suffix"` if [ -z "$dbdir" ]; then continue fi if ! is_empty_dir "$dbdir"; then echo >&2 \ " Directory $dbdir for $suffix not empty, aborting." exit 1 fi file="$dir/$suffix.ldif" printf ' - directory %s... ' "$suffix" >&2 # If there is an old DB_CONFIG file, restore it before # running slapadd backupdir="$(compute_backup_path -n "$suffix")" if [ -e "$backupdir"/DB_CONFIG ]; then cp -a "$backupdir"/DB_CONFIG "$dbdir"/ fi if [ -f "${SLAPD_CONF}" ]; then slapadd_opts="-g -f ${SLAPD_CONF}" else slapadd_opts="-g -F ${SLAPD_CONF}" fi capture_diagnostics slapadd ${slapadd_opts} \ -q -b "$suffix" -l "$file" || failed=1 if [ "$failed" ]; then rm -f "$dbdir"/* echo "failed." >&2 echo >&2 cat <<-EOF Loading the database from the LDIF dump failed with the following error while running slapadd: EOF release_diagnostics " " return 1 fi echo "done." >&2 if [ -n "$SLAPD_USER" ] || [ -n "$SLAPD_GROUP" ]; then echo -n " - chowning database directory ($SLAPD_USER:$SLAPD_GROUP)... " update_permissions "$dbdir" echo "done"; fi done } # }}} move_incompatible_databases_away() { # {{{ echo >&2 " Moving old database directories to /var/backups:" (get_suffix | while read -r suffix; do dbdir=`get_directory "$suffix"` move_old_database_away "$dbdir" "$suffix" <&5 done) 5<&0 # XXX: should ask the user via debconf # or maybe just use database_dumping_destdir local basedn ok_exists if [ "$1" = "-n" ]; then ok_exists=yes shift fi basedn="$1" local id target if [ "$MODE" = reconfigure ] || [ "$DEBCONF_RECONFIGURE" ]; then # reconfigure: use OLD_VERSION plus a timestamp. id="${OLD_VERSION:+$OLD_VERSION-}$(date +%Y%m%d-%H%M%S)" else # install/upgrade: use OLD_VERSION or a timestamp, not both. id="${OLD_VERSION:-$(date +%Y%m%d-%H%M%S)}" fi target="/var/backups/$basedn-$id.ldapdb" if [ -e "$target" ] && [ -z "$ok_exists" ]; then echo >&2 echo >&2 " Backup path $target exists. Giving up..." exit 1 fi printf '%s' "$target" } # }}} move_old_database_away() { # {{{ # Move the old database away if it is still there # # In fact this function makes sure that the database directory is empty # with the exception of any DB_CONFIG file # and can be populated with a new database. If something is in the way # it is moved to a backup directory if the user accepted the debconf # option slapd/move_old_database. Otherwise we output a warning and let # the user fix it himself. # Usage: move_old_database_away [] local databasedir backupdir databasedir="$1" suffix="${2:-unknown}" if [ ! -e "$databasedir" ] || is_empty_dir "$databasedir"; then return 0 fi # Note that we can't just move the database dir as it might be # a mount point. Instead me move the content which might # include mount points as well anyway, but it's much less likely. db_get slapd/move_old_database if [ "$RET" = true ]; then backupdir="$(compute_backup_path "$suffix")" printf ' - directory %s... ' "$suffix" >&2 mkdir -p "$backupdir" find -H "$databasedir" -mindepth 1 -maxdepth 1 -type f \ -exec mv {} "$backupdir" \; echo done. >&2 else cat >&2 <&2 " Moving old database directory to /var/backups:" move_old_database_away /var/lib/ldap fi create_ldap_directories create_new_slapd_conf "$basedn" create_new_directory "$basedn" "$dc" # Put the right permissions on this directory. update_permissions /var/lib/ldap # Now that we created the new directory we don't need the passwords in the # debconf database anymore. So wipe them. wipe_admin_pass } # }}} create_new_slapd_conf() { # {{{ # Create the new slapd.d directory (configuration) # Usage: create_new_slapd_conf local initldif failed basedn adminpass # Fetch configuration basedn="$1" db_get slapd/internal/adminpw adminpass="$RET" echo -n " Creating initial configuration... " >&2 # Create the slapd.d directory. rm -rf ${SLAPD_CONF}/cn=config ${SLAPD_CONF}/cn=config.ldif mkdir -p ${SLAPD_CONF} initldif=`mktemp -t slapadd.XXXXXX` cat /usr/share/slapd/slapd.init.ldif > ${initldif} # Change some defaults sed -i -e "s|@SUFFIX@|$basedn|g" ${initldif} sed -i -e "s|@PASSWORD@|$adminpass|g" ${initldif} capture_diagnostics slapadd -F "${SLAPD_CONF}" -b "cn=config" \ -l "${initldif}" || failed=1 if [ "$failed" ]; then cat <<-EOF Loading the initial configuration from the ldif file (${init_ldif}) failed with the following error while running slapadd: EOF release_diagnostics " " exit 1 fi update_permissions "${SLAPD_CONF}" rm -f "${initldif}" echo "done." >&2 } # }}} create_new_directory() { # {{{ # Create a new directory. Takes the basedn and the dc value of that entry. # Other information is extracted from debconf. # Usage: create_new_directory local basedn dc organization adminpass basedn="$1" dc="$2" db_get shared/organization organization="$RET" db_get slapd/internal/adminpw adminpass="$RET" echo -n " Creating LDAP directory... " >&2 initldif=`mktemp -t slapadd.XXXXXX` cat <<-EOF > "${initldif}" dn: $basedn objectClass: top objectClass: dcObject objectClass: organization o: $organization dc: $dc EOF capture_diagnostics slapadd -F "${SLAPD_CONF}" -b "${basedn}" \ -l "${initldif}" || failed=1 if [ "$failed" ]; then rm -f ${initldif} echo "failed." >&2 cat <<-EOF Loading the initial configuration from the ldif file (${init_ldif}) failed with the following error while running slapadd: EOF release_diagnostics " " exit 1 fi rm -f ${initldif} echo "done." >&2 } # }}} backup_config_once() { # {{{ # Create a backup of the current configuration files. # Usage: backup_config_once local backupdir if [ -z "$FLAG_CONFIG_BACKED_UP" ]; then if [ -e "$SLAPD_CONF" ]; then backupdir=`database_dumping_destdir` echo -n " Backing up $SLAPD_CONF in ${backupdir}... " >&2 cp -a "$SLAPD_CONF" "$backupdir" echo done. >&2 fi FLAG_CONFIG_BACKED_UP=yes fi } # }}} set_defaults_for_unseen_entries() { # {{{ # Set up the defaults for our templates DOMAIN=`hostname -d 2>/dev/null` || true if [ -z "$DOMAIN" ]; then DOMAIN='nodomain'; fi db_fget slapd/domain seen if [ "$RET" = false ]; then db_set slapd/domain "$DOMAIN" fi db_fget shared/organization seen if [ "$RET" = false ]; then db_set shared/organization "$DOMAIN" fi } # }}} crypt_admin_pass() { # {{{ # Store the encrypted admin password into the debconf db # Usage: crypt_admin_pass local adminpw; db_get slapd/password1 if [ ! -z "$RET" ]; then db_set slapd/internal/adminpw "$(create_password_hash "$RET")" else # Set the password. adminpw="$(generate_admin_pass)" db_set slapd/internal/generated_adminpw "$adminpw" db_set slapd/internal/adminpw "$(create_password_hash "$adminpw")" fi } generate_admin_pass() { # Generate a password, if no password given then generate one. # Usage: generate_admin_pass # 15 bytes of /dev/urandom provide 120 random bits, assuming the entropy pool is full enough. # Coding these 15 bytes in base64 returns a 20 characters long password. head -c 15 /dev/urandom | base64 | tr -d '[:space:]' } wipe_admin_pass() { # Remove passwords after creating the initial ldap database. # Usage: wipe_admin_pass db_set slapd/password1 "" db_set slapd/password2 "" db_set slapd/internal/adminpw "" db_set slapd/internal/generated_adminpw "" } # }}} create_password_hash() { # {{{ # Create the password hash for the given password # Usage: hash=`create_password_hash "$password"` slappasswd -s "$1" } # }}} previous_version_older() { # {{{ # Check if the previous version is newer than the reference version passed. # If we are not upgrading the previous version is assumed to be newer than # any reference version. # Usage: previous_version_older if dpkg --compare-versions "$OLD_VERSION" lt-nl "$1"; then return 0 else return 1 fi } # }}} previous_version_newer() { # {{{ # Check if the previous version is newer than the reference version passed. # If we are not upgrading the previous version is assumed to be newer than # any reference version. # Usage: previous_version_newer if dpkg --compare-versions "$OLD_VERSION" gt-nl "$1"; then return 0 else return 1 fi } # }}} is_initial_configuration() { # {{{ # Check if this is the initial configuration and not an upgrade of an # existing configuration # Usage: if is_initial_configuration "$@"; then ... fi from top level # Plain installation if [ "$1" = configure ] && [ -z "$2" ]; then return 0 fi # Configuration via dpkg-reconfigure if [ "$1" = reconfigure ] || [ "$DEBCONF_RECONFIGURE" ]; then return 0 fi # Upgrade but slapd.conf doesn't exist. If the user is doing this # intentionally because they want to put it somewhere else, they # should select manual configuration in debconf. if [ "$1" = configure ] && [ ! -e "${SLAPD_CONF}" ]; then return 0 fi return 1 } # }}} is_empty_dir() { # {{{ # Check if a path refers to a directory that is "empty" from the POV of slapd # (i.e., contains no files except for an optional DB_CONFIG). # Usage: if is_empty_dir "$dir"; then ... fi output=`find -H "$1" -mindepth 1 -maxdepth 1 -type f \! -name DB_CONFIG 2>/dev/null` if [ -n "$output" ]; then return 1 else return 0 fi } # }}} # ===== Global variables ================================================ {{{ # # At some points we need to know which version we are upgrading from if # any. More precisely we only care about the configuration and data we # might have laying around. Some parts also want to know which mode the # script is running in. MODE="$1" # install, upgrade, etc. - see debian-policy OLD_VERSION="$2" # Source the init script configuration # See example file debian/slapd.default for variables defined here if [ -f "/etc/default/slapd" ]; then . /etc/default/slapd fi # Load the default location of the slapd config file if [ -z "$SLAPD_CONF" ]; then if [ -f "/etc/ldap/slapd.conf" ] && \ [ ! -e "/etc/ldap/slapd.d" ] then SLAPD_CONF="/etc/ldap/slapd.conf" else SLAPD_CONF="/etc/ldap/slapd.d" fi fi # }}} # ----- Handling diagnostic output ------------------------------------ {{{ # # Often you want to run a program while you are showing progress # information to the user. If the program you are running outputs some # diagnostics it will mess up your screen. # # This is what the following functions are designed for. When running the # program, use capture_diagnostics to store what the program outputs to # stderr and use release_diagnostics to write out the captured output. capture_diagnostics() { # {{{ # Run the command passed and capture the diagnostic output in a temporary # file. You can dump that file using release_diagnostics. # Create the temporary file local tmpfile tmpfile=`mktemp` exec 7<>"$tmpfile" rm "$tmpfile" # Run the program and capture stderr. If the program fails the # function fails with the same status. "$@" 2>&7 || return $? } # }}} release_diagnostics() { # {{{ # Dump the diagnostic output captured via capture_diagnostics, optionally # prefixing each line. # Usage: release_diagnostics "prefix" { exec < /dev/stdin ; sed -e "s/^/$1/" ; } <&7 } # }}} # }}} # vim: set sw=8 foldmethod=marker: # Check if the user wants to configure slapd manually want_manual_configuration() { db_input medium slapd/no_configuration || true db_go || true db_get slapd/no_configuration no_configuration="$RET" if [ "$no_configuration" = "true" ]; then return 0 fi return 1 } # Make sure the values entered make sense validate_initial_config() { local invalid invalid="" # Make sure the domain name is valid # The regexp doesn't work for UTF-8 domain names, but for that to # work, we would also need to Base64 encode it in the LDIF; since # we're not doing it at the moment, this should be fine for now db_get slapd/domain if [ -z "$RET" ] || ! echo "$RET" | LC_COLLATE='C.UTF-8' grep -q '^[a-zA-Z0-9.-]*$'; then db_fset slapd/domain seen false invalid=true fi # Suffix and Organization may not be empty db_get shared/organization if [ -z "$RET" ]; then db_fset shared/organization seen false invalid=true fi # Make sure the passwords match local pass1 pass2 db_get slapd/password1 pass1="$RET" db_get slapd/password2 pass2="$RET" if [ "$pass1" != "$pass2" ]; then db_fset slapd/password1 seen false db_fset slapd/password2 seen false invalid=true fi # Tell the user if [ "$invalid" ]; then db_fset slapd/invalid_config seen false db_input critical slapd/invalid_config || true db_go || true db_get slapd/invalid_config if [ "$RET" != "true" ]; then db_set slapd/no_configuration true invalid= fi fi if [ "$invalid" ]; then return 1 else return 0 fi } # Query the information we need to create an initial directory query_initial_config() { while true; do db_input medium slapd/domain || true db_input medium shared/organization || true db_input high slapd/password1 || true db_input high slapd/password2 || true db_input low slapd/purge_database || true # XXX - should be done more general, but for now this should do # the trick if [ -e "/var/lib/ldap" ] && ! is_empty_dir /var/lib/ldap; then db_input low slapd/move_old_database || true fi db_go || true if validate_initial_config; then break fi done } # ----- Configuration of LDIF dumping and reloading--------------------- {{{ # # Dumping the database can have negative effects on the system we are # running on. If there is a lot of data dumping it might fill a partition # for example. Therefore we must give the user exact control over what we # are doing. configure_dumping() { # {{{ # Ask the user for the configuration of the dumping component # Usage: configure_dumping # Look if the user wants to migrate to the BDB backend if ! database_dumping_enabled; then return 0 fi # Configure if and where to dump the LDAP databases db_input medium slapd/dump_database || true db_go || true db_get slapd/dump_database # Abort if the user does not want dumping if [ "$RET" = never ]; then return 0 fi db_input medium slapd/dump_database_destdir || true db_go || true # If the user entered the empty value, go back to the default db_get slapd/dump_database_destdir if [ "$RET" = "" ]; then db_reset slapd/dump_database_destdir fi } # }}} # }}} # Create an initial directory on fresh install if is_initial_configuration "$@"; then if ! want_manual_configuration; then set_defaults_for_unseen_entries query_initial_config fi fi # Configure the dumping component if we are upgrading some older version. if [ "$1" = configure ] && [ -n "$2" ]; then configure_dumping fi db_go || true exit 0