#!/bin/bash # # Stand-alone LDAP Daemon (slapd) # # Description: Manages Stand-alone LDAP Daemon (slapd) as an OCF resource in # an high-availability setup. # # Authors: Jeroen Koekkoek # nozawat@gmail.com # John Keith Hohm # # License: GNU General Public License (GPL) # Copyright: (C) 2011 Pagelink B.V. # # The OCF code was inspired by the Postfix resource script written by # Raoul Bhatia <r.bhatia@ipax.at>. # # The code for managing the slapd instance is based on the the slapd init # script found in Debian GNU/Linux 6.0. # # OCF parameters: # OCF_RESKEY_slapd # OCF_RESKEY_ldapsearch # OCF_RESKEY_config # OCF_RESKEY_pidfile # OCF_RESKEY_user # OCF_RESKEY_group # OCF_RESKEY_services # OCF_RESKEY_watch_suffix # OCF_RESKEY_ignore_suffix # OCF_RESKEY_bind_dn # OCF_RESKEY_password # OCF_RESKEY_parameters # OCF_RESKEY_stop_escalate # ################################################################################ # Initialization: : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat} . ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs : ${OCF_RESKEY_slapd="/usr/sbin/slapd"} : ${OCF_RESKEY_ldapsearch="ldapsearch"} : ${OCF_RESKEY_config=""} : ${OCF_RESKEY_pidfile=""} : ${OCF_RESKEY_user=""} : ${OCF_RESKEY_group=""} : ${OCF_RESKEY_services="ldap:///"} : ${OCF_RESKEY_watch_suffix=""} : ${OCF_RESKEY_ignore_suffix=""} : ${OCF_RESKEY_bind_dn=""} : ${OCF_RESKEY_password=""} : ${OCF_RESKEY_parameters=""} : ${OCF_RESKEY_stop_escalate=30} USAGE="Usage: $0 {start|stop|status|monitor|validate-all|meta-data}" ORIG_IFS=$IFS NEWLINE=' ' ################################################################################ usage() { echo $USAGE >&2 } meta_data() { cat <<END <?xml version="1.0"?> <!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> <resource-agent name="slapd"> <version>0.1</version> <longdesc lang="en"> Resource script for Stand-alone LDAP Daemon (slapd). It manages a slapd instance as an OCF resource. </longdesc> <shortdesc lang="en">Manages a Stand-alone LDAP Daemon (slapd) instance</shortdesc> <parameters> <parameter name="slapd" unique="0" required="0"> <longdesc lang="en"> Full path to the slapd binary. For example, "/usr/sbin/slapd". </longdesc> <shortdesc lang="en">Full path to slapd binary</shortdesc> <content type="string" default="/usr/sbin/slapd" /> </parameter> <parameter name="ldapsearch" unique="0" required="0"> <longdesc lang="en"> Full path to the ldapsearch binary. For example, "/usr/bin/ldapsearch". </longdesc> <shortdesc lang="en">Full path to ldapsearch binary</shortdesc> <content type="string" default="ldapsearch" /> </parameter> <parameter name="config" required="0" unique="1"> <longdesc lang="en"> Full path to a slapd configuration directory or a slapd configuration file. For example, "/etc/ldap/slapd.d" or "/etc/ldap/slapd.conf". </longdesc> <shortdesc>Full path to configuration directory or file</shortdesc> <content type="string" default=""/> </parameter> <parameter name="pidfile" required="0" unique="0"> <longdesc lang="en"> File to read the PID from; read from olcPidFile/pidfile in config if not set. </longdesc> <shortdesc lang="en">File to read PID from</shortdesc> <content type="string" default="" /> </parameter> <parameter name="user" unique="0" required="0"> <longdesc lang="en"> User name or id slapd will run with. The group id is also changed to this user's gid, unless the group parameter is used to override. </longdesc> <shortdesc lang="en">User name or id slapd will run with</shortdesc> <content type="string" default="" /> </parameter> <parameter name="group" unique="0" required="0"> <longdesc lang="en"> Group name or id slapd will run with. </longdesc> <shortdesc lang="en">Group name or id slapd will run with</shortdesc> <content type="string" default="" /> </parameter> <parameter name="services" required="0" unique="1"> <longdesc lang="en"> LDAP (and other scheme) URLs slapd will serve. For example, "ldap://127.0.0.1:389 ldaps:/// ldapi:///" </longdesc> <shortdesc>LDAP (and other scheme) URLs to serve</shortdesc> <content type="string" default="ldap:///"/> </parameter> <parameter name="watch_suffix" required="0" unique="0"> <longdesc lang="en"> Suffix (database backend) that will be monitored for availability. Multiple suffixes can be specified by providing a space seperated list. By providing one or more suffixes here, the ignore_suffix parameter is discarded. All suffixes will be monitored if left blank. </longdesc> <shortdesc>Suffix that will be monitored for availability.</shortdesc> <content type="string" default=""/> </parameter> <parameter name="ignore_suffix" required="0" unique="0"> <longdesc lang="en"> Suffix (database backend) that will not be monitored for availability. Multiple suffixes can be specified by providing a space seperated list. No suffix will be excluded if left blank. </longdesc> <shortdesc>Suffix that will not be monitored for availability.</shortdesc> <content type="string" default=""/> </parameter> <parameter name="bind_dn" required="0" unique="0"> <longdesc lang="en"> Distinguished Name used to bind to the LDAP directory for testing. Leave blank to bind to the LDAP directory anonymously. </longdesc> <shortdesc>Distinguished Name used to bind to the LDAP directory for testing.</shortdesc> <content type="string" default=""/> </parameter> <parameter name="password" required="0" unique="0"> <longdesc lang="en"> Password used to bind to the LDAP directory for testing. </longdesc> <shortdesc>Password used to bind to the LDAP directory for testing.</shortdesc> <content type="string" default=""/> </parameter> <parameter name="parameters" unique="0" required="0"> <longdesc lang="en"> slapd may be called with additional parameters. Specify any of them here. </longdesc> <shortdesc lang="en">Any additional parameters to slapd.</shortdesc> <content type="string" default="" /> </parameter> <parameter name="stop_escalate" unique="0" required="0"> <longdesc lang="en"> Number of seconds to wait for shutdown (using SIGTERM) before resorting to SIGKILL </longdesc> <shortdesc lang="en">Seconds before stop escalation to KILL</shortdesc> <content type="integer" default="30" /> </parameter> </parameters> <actions> <action name="start" timeout="20s" /> <action name="stop" timeout="20s" /> <action name="monitor" depth="0" timeout="20s" interval="60s" /> <action name="validate-all" timeout="20s" /> <action name="meta-data" timeout="5s" /> </actions> </resource-agent> END } terminate() { local pid=$1 local signal=$2 local recheck=${3-0} local result local waited=0 kill -$signal $pid >/dev/null 2>&1; result=$? while [ $result -eq 0 ] && [ $recheck -eq 0 ] || [ $waited -lt $recheck ]; do kill -0 $pid >/dev/null 2>&1; result=$? let "waited += 1" if [ $result -eq 0 ]; then sleep 1 fi done if [ $result -ne 0 ]; then return 0 fi return 1 } watch_suffix() { local result if [ -n "$OCF_RESKEY_watch_suffix" ]; then if echo "'$OCF_RESKEY_watch_suffix'" | grep "'$1'" >/dev/null 2>&1; then result=0 else result=1 fi else if echo "'$OCF_RESKEY_ignore_suffix'" | grep "'$1'" >/dev/null 2>&1; then result=1 else result=0 fi fi return $result } slapd_pid() { local pid if [ -f "$pid_file" ]; then pid=`head -n 1 "$pid_file" 2>/dev/null` if [ "X$pid" != "X" ]; then echo "$pid" return $OCF_SUCCESS fi ocf_log err "slapd pid file '$pid_file' empty." return $OCF_ERR_GENERIC fi ocf_log info "slapd pid file '$pid_file' does not exist." return $OCF_NOT_RUNNING } slapd_status() { local pid=$1 local state=$? if [ $state -eq $OCF_SUCCESS ]; then if ! kill -0 $pid >/dev/null 2>&1; then return $OCF_NOT_RUNNING else return $OCF_SUCCESS fi fi return $state } slapd_start() { local options local reason local result local state slapd_status `slapd_pid`; state=$? if [ $state -eq $OCF_SUCCESS ]; then ocf_log info "slapd already running." return $state elif [ $state -eq $OCF_ERR_GENERIC ]; then return $state fi options="-u $user -g $group" if [ -d "$config" ]; then options="$options -F $config" elif [ -f "$config" ]; then options="$options -f $config" else ocf_log err "slapd configuration '$config' does not exist." return $OCF_ERR_INSTALLED fi if [ -n "$parameters" ]; then options="$options $parameters" fi if [ -n "$services" ]; then $slapd -h "$services" $options 2>&1; result=$? else $slapd $options 2>&1; result=$? fi if [ $result -ne 0 ]; then ocf_log err "slapd returned error." return $OCF_ERR_GENERIC fi ocf_log info "slapd started." return $OCF_SUCCESS } slapd_stop() { local pid local result local state pid=`slapd_pid`; slapd_status $pid; state=$? if [ $state -eq $OCF_NOT_RUNNING ]; then ocf_log info "slapd already stopped." return $OCF_SUCCESS elif [ $state -eq $OCF_ERR_GENERIC ]; then return $state fi terminate $pid TERM $OCF_RESKEY_stop_escalate; result=$? if [ $result -ne 0 ]; then ocf_log err "slapd failed to stop. Escalating to KILL." terminate $pid KILL; result=$? fi if [ -f "$pid_file" ]; then rm -f "$pid_file" >/dev/null 2>&1 fi ocf_log info "slapd stopped." return $OCF_SUCCESS } slapd_monitor() { local options local result local state local suffix local suffixes slapd_status `slapd_pid`; state=$? if [ $state -eq $OCF_NOT_RUNNING ]; then ocf_log debug "slapd is stopped." return $state elif [ $state -ne $OCF_SUCCESS ]; then ocf_log err "slapd returned error." return $state fi if [ -d "$config" ]; then for suffix in `find "$config"/'cn=config' -type f -name olcDatabase* -exec \ sed -ne 's/^[[:space:]]*olcSuffix:[[:space:]]\+\(.\+\)/\1/p' {} \;` do suffix=${suffix#\"*} suffix=${suffix%\"*} if watch_suffix $suffix; then suffixes="$suffixes $suffix" fi done elif [ -f "$config" ]; then for suffix in `sed -ne 's/^[[:space:]]*suffix[[:space:]]\+\(.\+\)/\1/p' "$config"` do suffix=${suffix#\"*} suffix=${suffix%\"*} if watch_suffix $suffix; then suffixes="$suffixes $suffix" fi done else ocf_log err "slapd configuration '$config' does not exist." return $OCF_ERR_INSTALLED fi options="-LLL -s base -x" if [ -n "$bind_dn" ]; then options="$options -D '$bind_dn' -w '$password'" fi for suffix in $suffixes; do ocf_run "$ldapsearch" -H "$services" -b "$suffix" $options >/dev/null 2>&1; result=$? case "$result" in "0") ocf_log debug "slapd database with suffix '$suffix' reachable" ;; "49") ocf_log err "slapd database with suffix '$suffix' unreachable. Invalid credentials." return $OCF_ERR_CONFIGURED ;; *) ocf_log err "slapd database with suffix '$suffix' unreachable." state=$OCF_ERR_GENERIC ;; esac done return $state } slapd_validate_all() { check_binary "$slapd" check_binary "$ldapsearch" if [ -z "$pid_file" ]; then if [ -d "$config" ]; then pid_file=`sed -ne \ 's/^olcPidFile:[[:space:]]\+\(.\+\)[[:space:]]*/\1/p' \ "$config"/'cn=config.ldif' 2>/dev/null` elif [ -f "$config" ]; then pid_file=`sed -ne \ 's/^pidfile[[:space:]]\+\(.\+\)/\1/p' \ "$config" 2>/dev/null` else ocf_log err "slapd configuration '$config' does not exist." return $OCF_ERR_INSTALLED fi fi if [ -z "$user" ]; then user=`id -nu 2>/dev/null` elif ! id "$user" >/dev/null 2>&1; then ocf_log err "slapd user '$user' does not exist" return $OCF_ERR_INSTALLED fi if [ -z "$group" ]; then group=`id -ng 2>/dev/null` elif ! grep "^$group:" /etc/group >/dev/null 2>&1; then ocf_log err "slapd group '$group' does not exist" return $OCF_ERR_INSTALED fi return $OCF_SUCCESS } # # Main # slapd=$OCF_RESKEY_slapd ldapsearch=$OCF_RESKEY_ldapsearch config=$OCF_RESKEY_config user=$OCF_RESKEY_user group=$OCF_RESKEY_group services=$OCF_RESKEY_services bind_dn=$OCF_RESKEY_bind_dn password=$OCF_RESKEY_password parameters=$OCF_RESKEY_parameters pid_file=$OCF_RESKEY_pidfile if [ -z "$config" ]; then if [ -e "/etc/ldap/slapd.d" ]; then config="/etc/ldap/slapd.d" else config="/etc/ldap/slapd.conf" fi fi if [ $# -ne 1 ]; then usage exit $OCF_ERR_ARGS fi case $1 in meta-data) meta_data exit $OCF_SUCCESS ;; usage|help) usage exit $OCF_SUCCESS ;; esac slapd_validate_all [ $? -eq $OCF_SUCCESS ] || exit $? case $1 in status) slapd_status `slapd_pid`; state=$? if [ $state -eq $OCF_SUCCESS ]; then ocf_log debug "slapd is running." elif [ $state -eq $OCF_NOT_RUNNING ]; then ocf_log debug "slapd is stopped." fi exit $state ;; start) slapd_start exit $? ;; stop) slapd_stop exit $? ;; monitor) slapd_monitor; state=$? exit $state ;; validate-all) exit $OCF_SUCCESS ;; *) usage exit $OCF_ERR_UNIMPLEMENTED ;; esac