From 6953a0eb6173765937b3e217e60ed21bc5c4e3f3 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 21 Feb 2023 17:33:09 +0100 Subject: [PATCH] Adding scripts/set-ldap-inetuserstatus --- scripts/set-ldap-inetuserstatus | 501 ++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100755 scripts/set-ldap-inetuserstatus diff --git a/scripts/set-ldap-inetuserstatus b/scripts/set-ldap-inetuserstatus new file mode 100755 index 0000000..7e852c9 --- /dev/null +++ b/scripts/set-ldap-inetuserstatus @@ -0,0 +1,501 @@ +#!/bin/bash + +set -e +set -u + +BASE_NAME="$( basename ${0} )" +MY_REAL_NAME=$( readlink -f $0 ) +BIN_DIR=$( dirname "${MY_REAL_NAME}" ) +BASE_DIR=$( dirname "${BIN_DIR}" ) + +VERSION="0.1" + +if [[ -f "${BIN_DIR}/functions.rc" ]] ; then + . "${BIN_DIR}/functions.rc" +else + echo "Bash resource file '${BIN_DIR}/functions.rc' not found" >&2 + exit 5 +fi + +cd "${BASE_DIR}" || exit 99 + +declare -a INSTANCES=() +INSTANCES+=( 'dpx-legacy' ) +INSTANCES+=( 'dpx-prd' ) +INSTANCES+=( 'dpx-dev' ) +INSTANCES+=( 'spk-prd' ) +INSTANCES+=( 'spk-dev' ) + +declare -a USED_INSTANCES=() +YES="n" + +declare -A LDAP_URLS=() +LDAP_URLS['dpx-legacy']="ldap://ldap-legacy.pixelpark.com" +LDAP_URLS['dpx-prd']="ldaps://prd-ds.pixelpark.com" +LDAP_URLS['dpx-dev']="ldaps://dev-ldap2.pixelpark.com" +LDAP_URLS['spk-prd']="ldaps://live-ldap.spk.pixelpark.net" +LDAP_URLS['spk-dev']="ldaps://stage-ldap.spk.pixelpark.net" + +declare -A BIND_DNS=() +BIND_DNS['dpx-legacy']="cn=admin" +BIND_DNS['dpx-prd']="cn=admin" +BIND_DNS['dpx-dev']="cn=admin" +BIND_DNS['spk-prd']="cn=admin" +BIND_DNS['spk-dev']="cn=admin" + +declare -A BIND_PW_FILES=() +BIND_PW_FILES['dpx-legacy']="${HOME}/.private/ldap-admin-wonl.txt" +BIND_PW_FILES['dpx-prd']="${HOME}/.private/dirsrv-prd-dpx-admin-pwd-wonl.txt" +BIND_PW_FILES['dpx-dev']="${HOME}/.private/dirsrv-dev-dpx-admin-pwd-wonl.txt" +BIND_PW_FILES['spk-prd']="${HOME}/.private/dirsrv-live-spk-admin-pwd-wonl.txt" +BIND_PW_FILES['spk-dev']="${HOME}/.private/dirsrv-stage-spk-admin-pwd-wonl.txt" + +declare -A SUFFIXES=() +SUFFIXES['dpx-legacy']="o=isp" +SUFFIXES['dpx-prd']="o=isp" +SUFFIXES['dpx-dev']="o=isp" +SUFFIXES['spk-prd']="dc=spk,dc=pixelpark,dc=net" +SUFFIXES['spk-dev']="dc=spk,dc=pixelpark,dc=net" + +declare -a USERS=() + +detect_color + +DESCRIPTION=$( cat <<-EOF + Sets the value of inetUserStatus of the given user to 'Active', 'Inactive' or 'Deleted'. + + EOF +) + +TMP_LDIF_FILE= + +USER= +INSTANCE= +LDAP_URL= +BIND_DN= +BIND_PW_FILE= +SUFFIX= +USER_DN= +USER_CN= +USER_MAIL= +USER_NAME= +REQUESTED_STATUS= + +#------------------------------------------------------------------------------ +draw_line() { + if [[ "${QUIET}" == "y" ]] ; then + return 0 + fi + echo "---------------------------------------------------" +} + +#------------------------------------------------------------------------------ +empty_line() { + if [[ "${QUIET}" == "y" ]] ; then + return 0 + fi + echo +} + +#------------------------------------------------------------------------------ +usage() { + + local inst_out="" + local inst= + local i=0 + + for inst in "${INSTANCES[@]}" ; do + i=$(( $i + 1 )) + if [[ "${i}" != "1" ]] ; then + if [[ "${i}" == "${#INSTANCES[*]}" ]] ; then + inst_out+=" and " + else + inst_out+=", " + fi + fi + inst_out+="'${inst}'" + done + + cat <<-EOF + Usage: ${BASE_NAME} [Common Options] [-i INSTANCE] -i ...] <[-A] | [-I] | [-D]> USER1 [USER2 ...] + ${BASE_NAME} [-h|--help] + ${BASE_NAME} [-V|--version] + + Options: + -i|--instance INSTANCE + The LDAP instance to use as the target of this script. If left empty, all instances are changed. + May be one or more of ${inst_out}. + -A|--active Set the user status to 'Active'. Mutually exclusive to '-I' and '-D'. + -I|--inactive Set the user status to 'Inactive'. Mutually exclusive to '-A' and '-D'. + -D|--deleted Set the user status to 'Deleted'. Mutually exclusive to '-A' and '-I'. + + Common Options: + EOF + + echo "${STD_USAGE_MSG}" + +} + +#------------------------------------------------------------------------------ +get_options() { + + local tmp= + local short_options="i:AIDy${STD_SHORT_OPTIONS}" + local long_options="inst:,instance:,active,inactive,deleted,yes,${STD_LONG_OPTIONS}" + local inst= + + set +e + tmp=$( getopt -o "${short_options}" --long "${long_options}" -n "${BASE_NAME}" -- "$@" ) + ret="$?" + if [[ "${ret}" != 0 ]] ; then + echo "" >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + set -e + + # Note the quotes around `$TEMP': they are essential! + eval set -- "${tmp}" + eval_common_options "$@" + if [[ "${DEBUG}" == 'y' ]] ; then + declare -p REMAINING_OPTS + declare -p REMAINING_ARGS + fi + + local len="${#REMAINING_OPTS[*]}" + local i="0" + local j= + local arg= + while [[ "$i" -lt "${len}" ]] ; do + + arg="${REMAINING_OPTS[$i]}" + + case "${arg}" in + -i|--inst|--instance) + j=$(( $i + 1 )) + USED_INSTANCES+=("${REMAINING_OPTS[$j]}" ) + i=$(( $i + 2 )) + ;; + -A|--active) + if [[ -n "${REQUESTED_STATUS}" ]] ; then + error "Requested status was already set to '${RED}${REQUESTED_STATUS}${NORMAL}'." + echo "" >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + REQUESTED_STATUS="Active" + i=$(( i + 1 )) + ;; + -I|--inactive) + if [[ -n "${REQUESTED_STATUS}" ]] ; then + error "Requested status was already set to '${RED}${REQUESTED_STATUS}${NORMAL}'." + echo "" >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + REQUESTED_STATUS="Inactive" + i=$(( i + 1 )) + ;; + -D|--deleted) + if [[ -n "${REQUESTED_STATUS}" ]] ; then + error "Requested status was already set to '${RED}${REQUESTED_STATUS}${NORMAL}'." + echo "" >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + REQUESTED_STATUS="Deleted" + i=$(( i + 1 )) + ;; + -y|--yes) + YES="y" + i=$(( i + 1 )) + ;; + *) echo -e "Internal error - option '${RED}${arg}${NORMAL} was wrong!" + exit 1 + ;; + esac + done + + for user in "${REMAINING_ARGS[@]}"; do + if [[ "${user}" == '--' ]] ; then + continue + fi + USERS+=( "${user}" ) + done + + if [[ "${#USED_INSTANCES[*]}" == 0 ]] ; then + for inst in "${INSTANCES[@]}" ; do + USED_INSTANCES+=( "${inst}" ) + done + fi + + local instances_ok='y' + for inst in "${USED_INSTANCES[@]}" ; do + if [[ ! -v "LDAP_URLS[${inst}]" ]] ; then + instances_ok='n' + error "Given instance '${RED}${inst}${NORMAL}' is invalid." + fi + done + if [[ "${instances_ok}" == "n" ]] ; then + echo >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + + if [[ "${#USERS[*]}" == "0" ]] ; then + echo >&2 + warn "No user to set inetUserStatus given." + echo >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + + if [[ -z "${REQUESTED_STATUS}" ]] ; then + echo >&2 + error "No requested inetUserStatus given." + echo >&2 + echo -e "$( usage )" >&2 + exit 1 + fi + + + info "Setting ${CYAN}inetUserStatus${NORMAL} for all given users to '${GREEN}${REQUESTED_STATUS}${NORMAL}." + + if [[ "${SIMULATE}" == "y" ]] ; then + echo + echo -e "${CYAN}---------------------------------------------${NORMAL}" + echo -e " ${YELLOW}Simulation mode${NORMAL}" + echo -e " ${CYAN}Nothing will be done in real.${NORMAL}" + echo -e "${CYAN}---------------------------------------------${NORMAL}" + sleep 0.5 + fi + +} + +#------------------------------------------------------------------------------ +cleanup_tmp_file() { + if [[ -n "${LDIF_FILE}" ]] ; then + if [[ -e "${LDIF_FILE}" ]] ; then + debug "Removing temporary file '${CYAN}${LDIF_FILE}${NORMAL}' ..." + RM_force "${LDIF_FILE}" + fi + fi +} + +#------------------------------------------------------------------------------ +yes_or_no() { + + local msg="$1" + local timeout="5" + if [[ "$#" -gt 1 ]] ; then + timeout="$2" + fi + + if [[ "${YES}" == "y" ]] ; then + return 0 + fi + + local answer= + debug "Trying to get an answer with a timeout of ${timeout} seconds." + printf "${msg}" + if read -t "${timeout}" answer ; then + debug "Got an answer: '${answer}'" + else + echo + return 1 + fi + + local first_letter=$( echo "${answer}" | tr '[:upper:]' '[:lower:]' | sed -e 's/^\(.\).*/\1/' ) + if [[ "${first_letter}" == 'y' || "${first_letter}" == 'j' ]] ; then + return 0 + else + return 1 + fi + +} + +#------------------------------------------------------------------------------ +get_user_dn() { + + debug "Trying to detect the DN uf user '${CYAN}${USER}${NORMAL}' ..." + + if [[ ! -f "${BIND_PW_FILE}" ]] ; then + error "Did not found password file '${RED}${BIND_PW_FILE}${NORMAL}' of Bind DN '${CYAN}${BIND_DN}${NORMAL}' of LDAP instance '${GREEN}${INSTANCE}${NORMAL}'." + empty_line + exit 5 + fi + + USER_DN='' + USER_CN= + USER_MAIL= + USER_NAME= + filter="(&(objectClass=*)(uid=${USER}))" + cmd="ldapsearch -x -LLL -o ldif-wrap=no -H '${LDAP_URL}' " + cmd+="-b \"${SUFFIX}\" -x -D \"${BIND_DN}\" -y \"${BIND_PW_FILE}\" " + cmd+="\"${filter}\" dn cn mail uid" + # cmd+="\"${filter}\" dn cn mail | grep '^dn:' | sed -e 's/^dn:[ ][ ]*//i' | head -n 1" + debug "Executing: ${cmd}" + local user_info=$( eval ${cmd} ) + + local count=$( echo "${user_info}" | grep '^dn:' | wc -l ) + if [[ "${count}" == "0" ]] ; then + warn "Did not found DN of uid '${YELLOW}${USER}${NORMAL}'." + return 1 + fi + if [[ "${count}" -gt 1 ]] ; then + warn "User Id '${YELLOW}${USER}${NORMAL}' is not unique." + return 1 + fi + + USER_DN=$( echo "${user_info}" | grep '^dn:' | sed -e 's/^dn:[ ][ ]*//i' ) + USER_CN=$( echo "${user_info}" | grep '^cn:' ) + if echo "${USER_CN}" | grep -q '^cn::' ; then + USER_CN=$( echo "${USER_CN}" | sed -e 's/^cn::[ ][ ]*//i' | base64 -d ) + else + USER_CN=$( echo "${USER_CN}" | sed -e 's/^cn:[ ][ ]*//i' ) + fi + USER_MAIL=$( echo "${user_info}" | grep '^mail:' | sed -e 's/^mail:[ ][ ]*//i' | head -n 1 ) + + if [[ -n "${USER_CN}" ]] ; then + USER_NAME="${USER_CN}" + else + USER_NAME=$( echo "${user_info}" | grep '^uid:' | sed -e 's/^uid:[ ][ ]*//i' | head -n 1 ) + fi + + local question="Do you want to set inetUserStatus for User '${CYAN}${USER_NAME}${NORMAL}' (${CYAN}${USER_DN}${NORMAL}) [${RED}y${NORMAL}|${YELLOW}N${NORMAL}] ? " + if yes_or_no "${question}" ; then + info "Setting inetUserStatus for user '${CYAN}${USER_NAME}${NORMAL}' (${CYAN}${USER_DN}${NORMAL})." + return 0 + fi + info "User '${CYAN}${USER_NAME}${NORMAL}' (${CYAN}${USER_DN}${NORMAL}) will be left untouched." + return 1 + +} + +#------------------------------------------------------------------------------ +setting_user_status() { + + local filter= + local cmd= + local user_info= + local cur_user_status= + local req_user_status= + + info "Setting inetUserStatus of '${CYAN}${USER_NAME}${NORMAL}' to '${CYAN}${REQUESTED_STATUS}${NORMAL}' ..." + + req_user_status=$( echo "${REQUESTED_STATUS}" | tr '[:upper:]' '[:lower:]' ) + + filter="(inetUserStatus=*)" + cmd="ldapsearch -x -LLL -o ldif-wrap=no -H '${LDAP_URL}' " + cmd+="-b \"${USER_DN}\" -s base -D \"${BIND_DN}\" -y \"${BIND_PW_FILE}\" " + cmd+="\"${filter}\" inetUserStatus" + debug "Executing: ${cmd}" + user_info=$( eval ${cmd} ) + cur_user_status=$( echo "${user_info}" | \ + grep -i '^inetUserStatus:' | \ + sed -e 's/^[a-z]*: *//i' | \ + tr '[:upper:]' '[:lower:]' ) + + debug "Cur_user_status: ${cur_user_status}, req_user_status: ${req_user_status}" + + if [[ -z "${cur_user_status}" ]] ; then + warn "Entry '${YELLOW}${USER_DN}${NORMAL}' does not have the attribute '${CYAN}inetUserStatus${NORMAL}'." + return + fi + if [[ "${req_user_status}" == "${cur_user_status}" ]] ; then + info "Entry '${CYAN}${USER_DN}${NORMAL}' has already the inetUserStatus '${CYAN}${REQUESTED_STATUS}${NORMAL}'." + return + fi + + cat > "${LDIF_FILE}" <<-EOF + dn: ${USER_DN} + changetype: modify + replace: inetUserStatus + inetUserStatus: ${REQUESTED_STATUS} + - + + EOF + + if [[ "${VERBOSE}" == "y" ]] ; then + debug "Resulting LDIF:" + cat "${LDIF_FILE}" + fi + + cmd="ldapmodify -H \"${LDAP_URL}\" -x -D \"${BIND_DN}\" -y \"${BIND_PW_FILE}\"" + cmd+=" -f \"$( readlink -f "${LDIF_FILE}" )\"" + debug "Executing: ${cmd}" + if [[ "${SIMULATE}" != "y" ]] ; then + eval $cmd + fi + debug "Done." + +} + +#------------------------------------------------------------------------------ +perform_instance() { + + empty_line + draw_line + info "Setting inetUserStatus of user '${GREEN}${USER}${NORMAL}' in LDAP instance '${GREEN}${INSTANCE}${NORMAL}'." + empty_line + sleep 2 + + if get_user_dn ; then + : + else + return 0 + fi + + debug "Continue ..." + setting_user_status + +} + +#------------------------------------------------ +main() { + + get_options "$@" + + debug "Creating temporary LDIF file ..." + LDIF_FILE=$( mktemp disable-user.XXXXXXXX.ldif ) + debug "Temporary file is '${CYAN}${LDIF_FILE}${NORMAL}'." + + trap cleanup_tmp_file INT TERM EXIT ABRT + + empty_line + info "Checking existence of necessary password files ..." + local all_files_there="y" + for INSTANCE in "${USED_INSTANCES[@]}" ; do + BIND_PW_FILE="${BIND_PW_FILES[${INSTANCE}]}" + debug "Checking for '${CYAN}${BIND_PW_FILE}${NORMAL}'." + if [[ -f "${BIND_PW_FILE}" && -r "${BIND_PW_FILE}" ]] ; then + debug "Password file '${CYAN}${BIND_PW_FILE}${NORMAL}' exists and is readable." + else + error "Password file '${RED}${BIND_PW_FILE}${NORMAL}' does not exists or is not redable." + all_files_there="n" + fi + done + empty_line + + if [[ "${all_files_there}" == "n" ]] ; then + exit 6 + fi + + for USER in "${USERS[@]}" ; do + for INSTANCE in "${USED_INSTANCES[@]}" ; do + LDAP_URL="${LDAP_URLS[${INSTANCE}]}" + BIND_DN="${BIND_DNS[${INSTANCE}]}" + BIND_PW_FILE="${BIND_PW_FILES[${INSTANCE}]}" + SUFFIX="${SUFFIXES[${INSTANCE}]}" + perform_instance + done + done + + empty_line + info "Finished." + +} + +main "$@" + +# vim: et list -- 2.39.5