#   BAREOS® - Backup Archiving REcovery Open Sourced
#
#   Copyright (C) 2013-2025 Bareos GmbH & Co. KG
#
#   This program is Free Software; you can redistribute it and/or
#   modify it under the terms of version three of the GNU Affero General Public
#   License as published by the Free Software Foundation and included
#   in the file LICENSE.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#   Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#   02110-1301, USA.

BAREOS_SCRIPTS_DIR="${BAREOS_SCRIPTS_DIR:-@scriptdir@}"
BAREOS_CONFIG_LIB=${BAREOS_SCRIPTS_DIR}/bareos-config-lib.sh
BAREOS_CONFIG_DIR=${BAREOS_CONFIG_DIR:-@confdir@}
BAREOS_CONFIG_TEMPLATE_DIR=${BAREOS_CONFIG_TEMPLATE_DIR:-@configtemplatedir@}
BAREOS_SBIN_DIR=${BAREOS_SBIN_DIR:-@sbindir@}
BAREOS_DIRECTOR_BINARY=${BAREOS_DIRECTOR_BINARY:-${BAREOS_SBIN_DIR}/bareos-dir}
AWK="@AWK@"

SEC_GROUPS="tape disk"

BAREOS_WORKING_DIR="${BAREOS_WORKING_DIR:-@working_dir@}"
FILE_DAEMON_USER="${BAREOS_FD_GROUP:-@fd_user@}"
FILE_DAEMON_GROUP="${BAREOS_FD_GROUP:-@fd_group@}"
STORAGE_DAEMON_USER="${BAREOS_SD_USER:-@sd_user@}"
STORAGE_DAEMON_GROUP="${BAREOS_SD_GROUP:-@sd_group@}"
DIRECTOR_DAEMON_USER="${BAREOS_DIR_USER:-@dir_user@}"
DIRECTOR_DAEMON_GROUP="${BAREOS_DIR_GROUP:-@dir_group@}"
DAEMON_USER="$DIRECTOR_DAEMON_USER"
DAEMON_GROUP="$DIRECTOR_DAEMON_GROUP"

DB_VERSION="${BDB_VERSION:-@BDB_VERSION@}"
SQL_DDL_DIR="${SQL_DDL_DIR:-@scriptdir@/ddl}"

PASSWORD_SUBST="\
XXX_REPLACE_WITH_DIRECTOR_PASSWORD_XXX \
XXX_REPLACE_WITH_CLIENT_PASSWORD_XXX \
XXX_REPLACE_WITH_STORAGE_PASSWORD_XXX \
XXX_REPLACE_WITH_DIRECTOR_MONITOR_PASSWORD_XXX \
XXX_REPLACE_WITH_CLIENT_MONITOR_PASSWORD_XXX \
XXX_REPLACE_WITH_STORAGE_MONITOR_PASSWORD_XXX \
"

os_type=$(uname -s)

is_function()
{
  func=${1-}
  test "$func" && command -v "$func" >/dev/null 2>&1
  return $?
}

# does not work on all shells (only bash),
# therefore removed until a better solution is found
# list_functions()
# {
#     if type typeset >/dev/null 2>&1; then
#         # show available shell functions,
#         # but exclude internal functions (name starts with "_" ...)
#         typeset -F | cut -d " " -f 3 | grep "^[a-z]"
#     else
#         echo "function list not available"
#     fi
# }

contains()
{
  haystack="$1"
  needle="$2"
  if [ "${haystack#*"$needle"}" != "$haystack" ]; then
    return 0 # $needle is in $haystack
  else
    return 1 # $needle is not in $haystack
  fi
}

info()
{
  echo "Info: $*" >&2
}

warn()
{
  echo "Warning: $*" >&2
}

error()
{
  echo "Error: $*" >&2
}

handle_database_scripts_command_line_parameter()
{
  # Check if some parameters are given, try to detect old usage with db_type
  if [ $# -gt 0 ]; then
    force=${1-}
    if [ "${force}" = "-f" ]; then
      export FORCE="yes"
      shift || true
    fi
    db_type=${1-}
    if [ "${db_type}" = "postgresql" ]; then
      warn "Parameter db_type is no more needed"
    elif [ -n "$db_type" ]; then
      error "db_type ${db_type} is no more supported, only postgresql"
      exit 1
    fi
  fi
}

get_config_lib_file()
{
  # can be used in following way:
  # LIB=`bareos-config get_config_lib_file`
  # . $LIB
  echo "${BAREOS_CONFIG_LIB}"
}

get_user_fd()
{
  echo "${FILE_DAEMON_USER}"
}

get_group_fd()
{
  echo "${FILE_DAEMON_GROUP}"
}

get_user_sd()
{
  echo "${STORAGE_DAEMON_USER}"
}

get_group_sd()
{
  echo "${STORAGE_DAEMON_GROUP}"
}

get_user_dir()
{
  echo "${DIRECTOR_DAEMON_USER}"
}

get_group_dir()
{
  echo "${DIRECTOR_DAEMON_GROUP}"
}

get_bareos_user()
{
  echo "${DAEMON_USER}"
}

get_bareos_dir()
{
  echo "${DAEMON_GROUP}"
}

get_working_dir()
{
  echo "${BAREOS_WORKING_DIR}"
}

get_database_ddl_dir()
{
  echo "${SQL_DDL_DIR}"
}

get_database_version()
{
  echo "${DB_VERSION}"
}

get_database_version_by_release()
{
  param="$1"

  # get release from parameter by stripping everything after the "-".
  # Example parameter: 13.2.2-926.1
  release=$(echo "${param}" | sed 's/-.*//')
  if [ -z "$release" ]; then
    error "failed to get database version for release ${param}"
    return 1
  fi

  MAP="${SQL_DDL_DIR}/versions.map"
  if [ ! -r "${MAP}" ]; then
    error "failed to read Bareos database versions map file ${MAP}"
    return 1
  fi

  MARKER=0
  VERSIONS=$(
    cat "${MAP}"
    echo "$release=${MARKER}"
  )

  # add marker to list,
  # sort the list reverse, assuming versions x.y.z  and lines x.y.z=db_version,
  # get entry one line after marker
  db_version=$(printf "%s" "${VERSIONS}" | sort -t. -k 1,1rn -k 2,2rn -k 3,3rn | sed -r -n "/[0-9.]+.*=$MARKER$/{ N; s/(.*)=(.*)\n(.*)=(.*)/\4/p }")

  if [ -z "${db_version}" ]; then
    db_version=$(sed -n "s/^default=//p" "${MAP}")
    if [ "${db_version}" ]; then
      warn "no database version defined for release ${release} (${param}). Using default version: ${db_version}"
    else
      error "neither found database version for release ${release} (${param}), nor found default database version in map file ${MAP}"
      return 1
    fi
  fi

  echo "${db_version}"
  return
}

_scsicrypto_cmds="bareos-sd bcopy bextract bls bscan bscrypto btape"
_scsicrypto_config_file="${BAREOS_CONFIG_DIR}/.enable-cap_sys_rawio"
check_scsicrypto_capabilities()
{
  if [ "${os_type}" != "Linux" ]; then
    error "check_scsicrypto_capabilities() is not supported on this platform"
    return 1
  fi
  if [ "$(command -v getcap)" = "" ] || [ "$(command -v setcap)" = "" ]; then
    error "getcap and setcap commands are mandatory"
    return 1
  fi
  if [ -f "${_scsicrypto_config_file}" ]; then
    cap_missing_conf=0
  else
    cap_missing_conf=1
  fi
  for c in ${_scsicrypto_cmds}; do
    if [ -x "${BAREOS_SBIN_DIR}/${c}" ]; then
      if ! r="$(setcap -v cap_sys_rawio=ep "${BAREOS_SBIN_DIR}/${c}")"; then
        warn "${r}"
        cap_missing_conf=1
      fi
    fi
  done
  if [ ${cap_missing_conf} -ne 0 ]; then
    warn "One of the tools are not configured for scsi crypto."
    warn "Run set_scsicrypto_capabilities to fix capabilities."
  else
    info "All tools have cap_sys_rawio=ep set."
  fi
}

set_scsicrypto_capabilities()
{
  if [ "${os_type}" != "Linux" ]; then
    error "set_scsicrypto_capabilities() is not supported on this platform"
    return 1
  fi
  for c in ${_scsicrypto_cmds}; do
    if [ -x "${BAREOS_SBIN_DIR}/${c}" ]; then
      # Need to affect the bareos group (debian)
      if ! r="$(chgrp "${STORAGE_DAEMON_GROUP}" "${BAREOS_SBIN_DIR}/${c}")"; then
        error "Setting group to ${STORAGE_DAEMON_GROUP} failed on ${c}"
        return 1
      fi
      if ! r="$(chmod o-rwx "${BAREOS_SBIN_DIR}/${c}")"; then
        error "Setting chmod o-rwx failed on ${c}"
        return 1
      fi
      if ! r="$(setcap cap_sys_rawio=ep "${BAREOS_SBIN_DIR}/${c}")"; then
        error "setcap on ${c} has failed - ${r}"
        return 1
      fi
    fi
  done
  echo "cap_sys_rawio+ep capabilities enabled on $(date "+%F %X")" >"${_scsicrypto_config_file}"
}

unset_scsicrypto_capabilities()
{
  if [ "${os_type}" != "Linux" ]; then
    error "unset_scsicrypto_capabilities() is not supported on this platform"
    return 1
  fi
  for c in ${_scsicrypto_cmds}; do
    if [ -x "${BAREOS_SBIN_DIR}/${c}" ]; then
      if ! r="$(setcap -q -r "${BAREOS_SBIN_DIR}/${c}")"; then
        # Don't fail if binary had capabilities set.
        warn "setcap -r on ${c} has failed - ${r}"
      fi
      if ! r="$(chmod o+rx "${BAREOS_SBIN_DIR}/${c}")"; then
        error "Restoring chmod o+rx failed on ${c}"
        return 1
      fi
      if ! r="$(chgrp root "${BAREOS_SBIN_DIR}/${c}")"; then
        error "Restoring group to root failed on ${c}"
        return 1
      fi
    fi
  done
  rm -f "${_scsicrypto_config_file}"
}

setup_sd_user()
{
  #
  # Guaranties that storage-daemon user and group exists
  # and storage-daemon user belongs to the required groups.
  #
  # normally, storage-daemon user
  # is already installed by the package preinstall script.
  #
  if [ "${os_type}" != "Linux" ]; then
    error "setup_sd_user() is not supported on this platform"
    return 1
  fi

  getent group "${STORAGE_DAEMON_GROUP}" >/dev/null || groupadd -r "${STORAGE_DAEMON_GROUP}"

  # Build list of additional groups the user should be in.
  ADD_GROUPS=""
  for sec_group in ${SEC_GROUPS}; do
    if getent group "${sec_group}"; then
      [ -z "${ADD_GROUPS}" ] && ADD_GROUPS="${sec_group}" || ADD_GROUPS="${ADD_GROUPS},${sec_group}"
    fi
  done

  #
  # If the user doesn't exist create a new one otherwise modify it to have the wanted secondary groups.
  #
  if [ -z "${STORAGE_DAEMON_USER}" ]; then
    info "SKIPPED: no storage daemon user specified."
    return 0
  fi

  if getent passwd "${STORAGE_DAEMON_USER}" >/dev/null; then
    if [ "${STORAGE_DAEMON_USER}" != "root" ]; then
      #
      # If the user was already created before,
      # Make sure the correct primary group is set otherwise fix it.
      #
      if [ "$(id -gn "${STORAGE_DAEMON_USER}")" != "${STORAGE_DAEMON_GROUP}" ]; then
        usermod -g "${STORAGE_DAEMON_GROUP}" "${STORAGE_DAEMON_USER}" || warn "failed to add groups ${STORAGE_DAEMON_GROUP} to ${STORAGE_DAEMON_USER}"
      fi
      #
      # Add the storage_daemon_user to additional groups
      #
      if [ -n "${ADD_GROUPS}" ]; then
        usermod -a -G "${ADD_GROUPS}" "${STORAGE_DAEMON_USER}" || warn "failed: ${USERMOD_CMDLINE} ${STORAGE_DAEMON_USER}"
      fi
    fi
  else
    #
    # User doesn't exist so create a new storage_daemon_user with additional groups
    #
    useradd -r --comment "bareos" --home-dir "${BAREOS_WORKING_DIR}" -g "${STORAGE_DAEMON_GROUP}" -G "${ADD_GROUPS}" --shell /bin/false "${STORAGE_DAEMON_USER}" || warn "failed to create user ${STORAGE_DAEMON_USER}"
  fi
}

get_config_path()
{
  # configuration component (either file or bareos-dir, ...)
  local COMPONENT="${1}"

  if [ -f "${COMPONENT}" ]; then
    printf "%s" "${COMPONENT}"
  elif [ -f "${BAREOS_CONFIG_DIR}/${COMPONENT}.conf" ]; then
    printf "%s" "${BAREOS_CONFIG_DIR}/${COMPONENT}.conf"
  elif [ -d "${BAREOS_CONFIG_DIR}/${COMPONENT}.d/" ]; then
    printf "%s" "${BAREOS_CONFIG_DIR}/${COMPONENT}.d/"
  else
    warn "failed to find config for component \"${COMPONENT}\"."
    return 1
  fi
}

get_config_path2()
{
  # configuration component (either file or bareos-dir, ...)
  local BASEDIR="${1}"
  local COMPONENT="${2}"

  if ! [ -d "${BASEDIR}" ]; then
    warn "base directory \"${BASEDIR}\" does not exists."
    return 1
  fi

  if [ -f "${BASEDIR}/${COMPONENT}.conf" ]; then
    printf "%s" "${BASEDIR}/${COMPONENT}.conf"
  elif [ -d "${BASEDIR}/${COMPONENT}.d/" ]; then
    printf "%s" "${BASEDIR}/${COMPONENT}.d/"
  elif [ -d "${BASEDIR}/${COMPONENT}/" ]; then
    printf "%s" "${BASEDIR}/${COMPONENT}/"
  else
    warn "failed to find config for component \"${COMPONENT}\" in directory \"${BASEDIR}\"."
    return 1
  fi
}

deploy_config_usage()
{
  if [ "$1" ]; then
    error "$1"
  fi
  info "Usage: deploy_config [--init | --add-missing | --exist] <COMPONENT>"
}

deploy_config()
{
  # usage:
  #   deploy_config COMPONENT
  #   deploy_config --init COMPONENT
  #       0: config deployed or skipped, because dest already exists.
  #       >0: error, including, dest already exists.
  #   deploy_config --add-missing COMPONENT
  #       0: missing files added to target config.
  #          If no files are missing, stills returns 0.
  #       >0: error
  #   deploy_config --exist COMPONENT
  #     0: dest does not exist, so config would be deployed if called with --init
  #     >0: dest already exist
  #
  #   RC:
  #    2: wrong parameter, usage error.

  local COMPONENT

  [ -n "${BAREOS_CONFIG_TEMPLATE_DIR}" ] || return 1

  local MODE="init"
  if echo "$1" | grep -q '^--'; then
    if [ "$1" = "--init" ]; then
      # default
      MODE="init"
    elif [ "$1" = "--add-missing" ]; then
      MODE="add-missing"
    elif [ "$1" = "--exist" ]; then
      MODE="exist"
    else
      deploy_config_usage "unknown mode parameter ${MODE}"
      return 2
    fi
    shift
  fi

  COMPONENT="${1:-}"

  RC=0
  if [ "${MODE}" = "init" ]; then
    deploy_config_init "${COMPONENT}" || RC=$?
  elif [ "${MODE}" = "add-missing" ]; then
    deploy_config_add_missing "${COMPONENT}" || RC=$?
  elif [ "${MODE}" = "exist" ]; then
    config_exists "${COMPONENT}" || RC=$?
  else
    deploy_config_usage "unknown mode ${MODE}"
    return 2
  fi

  return $RC
}

config_exists()
{
  local COMPONENT=${1:-}

  if [ -z "$COMPONENT" ]; then
    deploy_config_usage "missing component"
    return 2
  fi

  local DEST_CONFIG_PATH
  DEST_CONFIG_PATH="$(get_config_path2 "${BAREOS_CONFIG_DIR}" "${COMPONENT}")"
  return $?
}

deploy_config_init()
{
  local COMPONENT=${1:-}

  if [ -z "$COMPONENT" ]; then
    deploy_config_usage "missing component"
    return 2
  fi

  local SOURCE_CONFIG_PATH
  if ! SOURCE_CONFIG_PATH="$(get_config_path2 "${BAREOS_CONFIG_TEMPLATE_DIR}" "${COMPONENT}")"; then
    return 1
  fi

  local DEST_CONFIG_PATH
  RC=0
  if [ -d "${SOURCE_CONFIG_PATH}" ]; then
    DEST_CONFIG_PATH="${BAREOS_CONFIG_DIR}/${COMPONENT}.d/"
    if [ -d "${DEST_CONFIG_PATH}" ]; then
      info "deploy_config_init(): destination path ${DEST_CONFIG_PATH} already exists. Skipped"
      return 0
    fi
    mkdir -p "${DEST_CONFIG_PATH}"
    # On macOS, only single letter cp options are available.
    # -r: --recursive
    cp -r "${SOURCE_CONFIG_PATH}/." "${DEST_CONFIG_PATH}" || RC=$?
  else
    DEST_CONFIG_PATH="${BAREOS_CONFIG_DIR}/${COMPONENT}.conf"
    if [ -f "${DEST_CONFIG_PATH}" ]; then
      info "deploy_config_init(): destination ${DEST_CONFIG_PATH} already exists. Skipped"
      return 0
    fi
    cp "${SOURCE_CONFIG_PATH}" "${DEST_CONFIG_PATH}" || RC=$?
  fi

  if [ $RC -eq 0 ]; then
    config_finalize "${BAREOS_CONFIG_DIR}" "${COMPONENT}" || RC=$?
  fi

  return $RC
}

deploy_config_add_missing()
{
  local COMPONENT=${1:-}

  if [ -z "$COMPONENT" ]; then
    deploy_config_usage "missing component"
    return 2
  fi

  local SOURCE_CONFIG_PATH
  if ! SOURCE_CONFIG_PATH="$(get_config_path2 "${BAREOS_CONFIG_TEMPLATE_DIR}" "${COMPONENT}")"; then
    return 1
  fi

  local DEST_CONFIG_PATH
  RC=0
  if [ -d "${SOURCE_CONFIG_PATH}" ]; then
    DEST_CONFIG_PATH="${BAREOS_CONFIG_DIR}/${COMPONENT}.d/"
    mkdir -p "${DEST_CONFIG_PATH}"

    #
    # Copy all files, but do not overwrite existing file.
    # Use verbose to detect, when new files have been copied.
    # On macOS, only single letter cp options are available.
    # -v: --verbose
    # -r: --recursive
    # -n: --no-clobber
    #
    OUT=$(cp -v -r -n "${SOURCE_CONFIG_PATH}/." "${DEST_CONFIG_PATH}")
    printf "%s" "${OUT}"
    if [ "${OUT}" ]; then
      RC=0
    fi

  else
    DEST_CONFIG_PATH="${BAREOS_CONFIG_DIR}/${COMPONENT}.conf"
    if [ -f "${DEST_CONFIG_PATH}" ]; then
      info "deploy_config_init(): destination ${DEST_CONFIG_PATH} already exists. Skipped"
      return 0
    fi
    cp "${SOURCE_CONFIG_PATH}" "${DEST_CONFIG_PATH}" || RC=$?
  fi

  if [ $RC -eq 0 ]; then
    config_finalize "${BAREOS_CONFIG_DIR}" "${COMPONENT}" || RC=$?
  fi

  return $RC
}

config_finalize()
{
  # usage:
  #   $1: DEST
  #   $2: COMPONENT

  local DEST=${1:-}
  local COMPONENT=${2:-}

  local DEST_CONFIG_PATH
  if ! DEST_CONFIG_PATH="$(get_config_path2 "${DEST}" "${COMPONENT}")"; then
    return 1
  fi

  if [ "${COMPONENT}" = "bareos-dir" ]; then
    configure_bareos_dir "${DEST}"
  elif [ "${COMPONENT}" = "tray-monitor" ]; then
    configure_tray_monitor "${DEST}"
  fi

  initialize_local_hostname
  initialize_passwords
  config_set_permissions "${DEST}" "${COMPONENT}"
}

config_set_permissions()
{
  # usage:
  #   $1: DEST
  #   $2: COMPONENT

  local DEST=${1:-}
  local COMPONENT=${2:-}

  local DEST_CONFIG_PATH
  if ! DEST_CONFIG_PATH="$(get_config_path2 "${DEST}" "${COMPONENT}")"; then
    return 1
  fi

  # defaults
  local COMPONENT_USER="${DAEMON_USER}"
  local COMPONENT_GROUP="${DAEMON_GROUP}"
  local FILE_PERMISSION="u=rw,g=r,o="
  local DIR_PERMISSION="u=rwx,g=rxs,o="

  if [ "$os_type" = "Darwin" ]; then
    # MacOS users from the "admin" group should be able to change the
    COMPONENT_USER=""
    COMPONENT_GROUP="admin"
    DIR_PERMISSION="ug=rwx,o="
    FILE_PERMISSION="ug=rw,o="
  else
    if [ "${COMPONENT}" = "bareos-fd" ]; then
      COMPONENT_USER="root"
    elif [ "${COMPONENT}" = "bconsole" ]; then
      COMPONENT_USER="root"
    elif [ "${COMPONENT}" = "tray-monitor" ]; then
      DIR_PERMISSION="u=rwx,g=rxs,o=rx"
      FILE_PERMISSION="u=rw,go=r"
    fi
  fi

  if [ -f "${DEST_CONFIG_PATH}" ]; then
    # only one config file
    if [ "${COMPONENT_USER}" ]; then
      chown "${COMPONENT_USER}" "${DEST_CONFIG_PATH}"
    fi
    if [ "${COMPONENT_GROUP}" ]; then
      chgrp "${COMPONENT_GROUP}" "${DEST_CONFIG_PATH}"
    fi
    chmod "${FILE_PERMISSION}" "${DEST_CONFIG_PATH}"
  else
    # config directory
    if [ "${COMPONENT_USER}" ]; then
      chown -R "${COMPONENT_USER}" "${DEST_CONFIG_PATH}"
    fi
    if [ "${COMPONENT_GROUP}" ]; then
      chgrp -R "${COMPONENT_GROUP}" "${DEST_CONFIG_PATH}"
    fi
    if ls "${DEST_CONFIG_PATH}"/*/*.conf >/dev/null 2>&1; then
      chmod ${DIR_PERMISSION} "${DEST_CONFIG_PATH}" "${DEST_CONFIG_PATH}"/*/
      chmod ${FILE_PERMISSION} "${DEST_CONFIG_PATH}"/*/*.conf
    fi
  fi

  if [ "${COMPONENT}" = "bareos-dir" ]; then
    # the bareos-dir uses config directories
    # bareos.d and bareos-dir-export (for exporting configuration).
    config_set_permissions "${DEST}" "bareos-dir-export"
  fi
}

configure_bareos_dir()
{
  local DEST="$1"
  local COMPONENT="bareos-dir"

  mkdir -p "${DEST}/bareos-dir-export/client/"
}

configure_tray_monitor()
{
  local DEST="$1"
  local COMPONENT="tray-monitor"

  local SOURCE_CONFIG_PATH
  if ! SOURCE_CONFIG_PATH="$(get_config_path2 "${BAREOS_CONFIG_TEMPLATE_DIR}" "${COMPONENT}")"; then
    return 1
  fi

  local DEST_CONFIG_PATH
  if ! DEST_CONFIG_PATH="$(get_config_path2 "${DEST}" "${COMPONENT}")"; then
    return 1
  fi

  if ! [ -d "${SOURCE_CONFIG_PATH}" ] || ! [ -d "${DEST_CONFIG_PATH}" ]; then
    return 1
  fi

  if config_exists "bareos-fd"; then
    if ! [ -e "${DEST_CONFIG_PATH}/client/FileDaemon-local.conf" ]; then
      cp "${SOURCE_CONFIG_PATH}/client/FileDaemon-local.conf" "${DEST_CONFIG_PATH}/client/FileDaemon-local.conf"
    fi
  fi

  if config_exists "bareos-sd"; then
    if ! [ -e "${DEST_CONFIG_PATH}/storage/StorageDaemon-local.conf" ]; then
      cp "${SOURCE_CONFIG_PATH}/storage/StorageDaemon-local.conf" "${DEST_CONFIG_PATH}/storage/StorageDaemon-local.conf"
    fi
  fi

  if config_exists "bareos-dir"; then
    if ! [ -e "${DEST_CONFIG_PATH}/director/Director-local.conf" ]; then
      cp "${SOURCE_CONFIG_PATH}/director/Director-local.conf" "${DEST_CONFIG_PATH}/director/Director-local.conf"
    fi
    if ! [ -e "${DEST_CONFIG_PATH}/monitor/bareos-mon.conf" ]; then
      cp "${SOURCE_CONFIG_PATH}/monitor/bareos-mon.conf" "${DEST_CONFIG_PATH}/monitor/bareos-mon.conf"
    fi
  fi
}

get_config_param()
{
  #
  # get parameter values from a Bareos configuration file
  #

  # configuration file
  local CFG_FILE="${1-}"
  # section, currently ignored
  local SECTION="${2-}"
  # name of the section, currently ignored
  local NAME="${3-}"
  # parameter to get from config file
  local PARAM="${4-}"
  # default value, if parameter is not found
  local DEFAULT="${5-}"

  if [ ! -r "${CFG_FILE}" ]; then
    warn "failed to get parameter ${SECTION} ${NAME} ${PARAM}: can't read ${CFG_FILE}"
    # if default value is given, return it anyway
    [ "${DEFAULT}" ] && echo "${DEFAULT}"
    return 1
  fi

  # get parameter from configuration file
  VALUE="$(grep -Ei "^[ 	]*${PARAM}[ 	]*=" "${CFG_FILE}" \
    | cut -d'=' -f2 \
    | sed -e 's/[ 	]*"//' -e 's/"//')"
  [ "${VALUE}" ] || VALUE="${DEFAULT}"
  echo "$VALUE"
}

set_config_param()
{
  #
  # set parameter values to a Bareos configuration file
  #

  # Limitations:
  # - multiple directives in one line,
  #   separated by ";"
  #   (could by handled by RS="\n|;", but this could cause problems with other lines)
  #   Possible workaround: get expanded config by "bareos-* --xc"
  # - "}" must be the only character in a line
  # - between "{" and the first directive must be a line break
  # - Name directive must come before directive to modify,
  #   preferably Name is the first directive of a resource
  # - does not handle includes ("@" and include scripts)
  #   Possible workaround: get expanded config by "bareos-* --xc"

  # configuration component (either file or bareos-dir, ...)
  local COMPONENT="${1}"
  # section / resource type
  local SECTION="${2}"
  # name of the section, currently ignored
  local NAME="${3}"
  # parameter to set in the config file
  local PARAM="${4}"
  # value to set
  local VALUE="${5}"

  local CONFIG_PATH
  if ! CONFIG_PATH=$(get_config_path "${COMPONENT}"); then
    return 1
  fi

  if [ -d "${CONFIG_PATH}" ]; then
    set_config_param_in_directory "${CONFIG_PATH}" "${SECTION}" "${NAME}" "${PARAM}" "${VALUE}"
  else
    set_config_param_in_file "${CONFIG_PATH}" "${SECTION}" "${NAME}" "${PARAM}" "${VALUE}"
  fi

  return
}

set_config_param_in_directory()
{
  if [ "${os_type}" != "Linux" ]; then
    error "set_config_param_in_directory() is not supported on this platform"
    return 1
  fi
  # configuration file
  local DIRECTORY="${1}"
  # section / resource type
  local SECTION=$(echo "${2}" | tr '[:upper:]' '[:lower:]')
  # name of the section, currently ignored
  local NAME="${3}"
  # parameter to set in the config file
  local PARAM="${4}"
  # value to set
  local VALUE="${5}"

  if ! [ -e "${DIRECTORY}/${SECTION}" ]; then
    warn "failed to set parameter ${SECTION} ${NAME} ${PARAM} = ${VALUE}: directory ${DIRECTORY}/${SECTION} does not exist"
    return 1
  fi

  FILE="${DIRECTORY}/${SECTION}/${NAME}.conf"
  if ! [ -f "${FILE}" ]; then
    printf "%s {\n  Name = %s\n}\n" "${SECTION}" "${NAME}" >"${FILE}"
  fi
  RC=0
  set_config_param_in_file "${FILE}" "${SECTION}" "${NAME}" "${PARAM}" "${VALUE}" || RC=$?
  return ${RC}
}

set_config_param_in_file()
{
  if [ "${os_type}" != "Linux" ]; then
    error "set_config_param() is not supported on this platform"
    return 1
  fi

  # configuration file
  local FILE="${1}"
  # section / resource type
  local SECTION="${2}"
  # name of the section, currently ignored
  local NAME="${3}"
  # parameter to set in the config file
  local PARAM="${4}"
  # value to set
  local VALUE="${5}"

  if [ ! -w "${FILE}" ]; then
    warn "failed to set parameter ${SECTION} ${NAME} ${PARAM} = ${VALUE}: can't access file '${FILE}'"
    return 1
  fi

  TEMP="$(mktemp)"
  RC=0
  $AWK -v SECTION="${SECTION}" -v NAME="${NAME}" -v PARAM="${PARAM}" -v VALUE="${VALUE}" '
  BEGIN {
    IGNORECASE = 1;
    FS = "=";
    # could add ; as Record Separator, but this may causes problems if line contains this character.
    #RS="\n|;"
    done = 0
    printed = 0
  };
  level == 1 && /}/ && resourcetype == SECTION && name == NAME && done == 0 {
        printf "  %s = %s\n", PARAM, VALUE
        printf "}\n"
        printed = 1
        done = 1
  };
  /}/ {
    resourcetype="";
    level--;
  };
  /{/ {
        level++;
  };
  level == 1 && /{/ {
        resourcetype=$1;
        gsub("[ {]","",resourcetype)
  };
  level == 1 && resourcetype == SECTION && $1 ~ /^[ ]*Name[ ]*$/ {
        name = $2
        gsub("[ \"]","",name)
        #printf "%s: %s\n", resourcetype, name
  };
  level == 1 && resourcetype == SECTION && name == NAME {
        param = $1
        gsub("[ \"]","",param)
        #printf "found %s => %s\n", param, $2
  };
  level == 1 && resourcetype == SECTION && name == NAME && param == PARAM {
        printf "%s= %s\n", $1, VALUE
        printed = 1
        done = 1
  };
  printed == 0 {
    print $0
  }
  printed == 1 {
    printed = 0
  }
    ' "${FILE}" >"${TEMP}" || RC=$?
  if [ ${RC} -ne 0 ]; then
    warn "failed to set parameter ${SECTION} ${NAME} ${PARAM} = ${VALUE}: replace failed"
    rm -f "${TEMP}"
    return 1
  fi

  chown --reference="${FILE}" "${TEMP}"
  chmod --reference="${FILE}" "${TEMP}"

  if ! mv "${TEMP}" "${FILE}"; then
    warn "failed to set parameter ${SECTION} ${NAME} ${PARAM} = ${VALUE}: replacing ${FILE} failed"
    rm -f "${TEMP}"
    return 1
  fi
}

get_database_param()
{
  PARAM="${1-}"
  DEFAULT="${2-}"
  VALUE=""
  CATALOG="${CATALOG:-MyCatalog}"

  # Check if PARAM is valid
  case "${PARAM}" in
    "dbaddress") ;;
    "dbsocket") ;;
    "dbport") ;;
    "dbname") ;;
    "dbuser") ;;
    "dbpassword") ;;
    *)
      if [ -n "${DEFAULT}" ]; then
        echo "${DEFAULT}"
      fi
      return 0
      ;;
  esac
  # Retrieve PARAM line in configuration section output
  # Beware we can be called before bareos-dir is installed on system (Debian like)
  if [ -x "${BAREOS_DIRECTOR_BINARY}" ]; then
    CONFIG=$(${BAREOS_DIRECTOR_BINARY} -c "${BAREOS_CONFIG_DIR}" --xc Catalog "$CATALOG")
    if ! VALUE="$(get_config_line_parameter "${CONFIG}" "${PARAM}")"; then
      # if default value is given, return it anyway
      if [ -n "${DEFAULT}" ]; then
        VALUE="${DEFAULT}"
      fi
    fi
  else
    warn "${BAREOS_DIRECTOR_BINARY} not found"
  fi
  [ -z "${VALUE}" ] && VALUE="${DEFAULT}"
  echo "${VALUE}"
  return 0
}

get_database_name()
{
  DEFAULT="${1-}"
  RC=0
  get_database_param "dbname" "$DEFAULT" || RC=$?
  return ${RC}
}

get_database_user()
{
  DEFAULT="${1-}"
  RC=0
  get_database_param "dbuser" "$DEFAULT" || RC=$?
  return ${RC}
}

get_database_password()
{
  DEFAULT="${1-}"
  RC=0
  get_database_param "dbpassword" "$DEFAULT" || RC=$?
  return ${RC}
}

get_config_line_parameter()
{
  CONFIG_RESOURCE_SECTION="$1"
  CONFIG_PARAM="$2"
  #For POSIX compliance use only *
  printf "%s" "${CONFIG_RESOURCE_SECTION}" | sed -n -e "s/[[:space:]]*${CONFIG_PARAM}[[:space:]]*=[[:space:]]*[\"]*\([^\"]*\)[\"]*[[:space:]]*$/\1/ip"
}

set_postgresql_environment_variables()
{
  CATALOG="${1:-MyCatalog}"

  if ! CONFIG=$(${BAREOS_DIRECTOR_BINARY} -c "${BAREOS_CONFIG_DIR}" --xc Catalog "${CATALOG}"); then
    error "Failed to get configuration of catalog $CATALOG"
    error "${CONFIG}"
    return 1
  fi

  export PGHOST="$(get_config_line_parameter "${CONFIG}" DbAddress)"
  export PGPORT="$(get_config_line_parameter "${CONFIG}" DbPort)"
  export PGSOCKET="$(get_config_line_parameter "${CONFIG}" DbSocket)"
  export PGDATABASE="$(get_config_line_parameter "${CONFIG}" DbName)"
  export PGUSER="$(get_config_line_parameter "${CONFIG}" DbUser)"
  export PGPASSWORD="$(get_config_line_parameter "${CONFIG}" DbPassword)"
}

is_template_sql_file()
{
  input_file=${1-}

  if [ -z "${input_file}" ]; then
    return 1
  fi

  if ! grep -E "@DB_NAME@|@DB_USER@|@DB_PASS@|@DB_VERSION@" "${input_file}" >/dev/null 2>&1; then
    # no variables found in file, this file is not a template.
    return 0
  else
    # variables found, this file is a template, therefore return FALSE
    return 1
  fi
}

get_unused_separator()
{
  # Try to find a separator character (for sed)
  # not used in source string.

  source="${1:-}"
  sep=""
  candidates="# , / % + - _ ; = ° §"
  for sep in ${candidates}; do
    if ! contains "${source}" "${sep}"; then
      printf "%s" "${sep}"
      return 0
    fi
  done
  return 1
}

get_translated_sql_file()
{
  # replaces variables in a SQL DDL file
  # and returns the result as stdout.

  input_file=${1-}

  if [ -z "${input_file}" ]; then
    return 1
  fi

  if [ ! -f "${input_file}" ]; then
    return 2
  fi

  db_name="${db_name:-$(get_database_name @db_name@)}"
  db_user="${db_user:-$(get_database_user @db_user@)}"
  # if $db_password is defined but empty, an empty password will be used ("-" instead of ":-")
  db_password="${db_password-$(get_database_password @db_password@)}"
  db_version=$(get_database_version)
  # Try to find a separator character (for sed) not used in db_password.
  RC=0
  sep=$(get_unused_separator "${db_password}") || RC=$?
  if [ ${RC} -ne 0 ]; then
    error "Not able to find a free separator for sed not used in your password"
    return 3
  fi

  if [ "${db_password}" != "" ]; then
    db_password="PASSWORD '${db_password}'"
  fi

  sed -e "s${sep}@DB_NAME@${sep}${db_name}${sep}" \
    -e "s${sep}@DB_USER@${sep}${db_user}${sep}" \
    -e "s${sep}@DB_PASS@${sep}${db_password}${sep}" \
    -e "s${sep}@DB_VERSION@${sep}${db_version}${sep}" \
    "${input_file}"
}

get_database_grant_privileges()
{
  # Returns the DDL for granting privileges for a database user.
  # Can be used, to get the SQL commands
  # to create additional database users.
  # If requested, this user can be limited to read-only database access.

  #db_name="$1"
  db_user="${2-}"
  db_password="${3-}"
  privileges="${4-}"
  case "${privileges}" in
    "")
      # full access
      privileges=""
      ;;
    "readonly")
      privileges="-readonly"
      ;;
    *)
      echo "Unknown privileges parameter $4"
      return 1
      ;;
  esac

  sql_definitions="${SQL_DDL_DIR}/grants/postgresql${privileges}.sql"

  if [ -n "${sql_definitions}" ]; then
    if [ ! -f "${sql_definitions}" ]; then
      echo "Unable to open database table definitions in file ${sql_definitions}"
      return 1
    fi
    get_translated_sql_file "${sql_definitions}"
  fi

  return
}

translate_sql_files()
{
  # Translates all available DDL files for one database type.
  # However, currently not used, because it reduced flexibility.

  SOURCE_DIR=${1:-$SQL_DDL_DIR}
  DEST_DIR=${2:-"@working_dir@/ddl"}

  for i in $(find "$SOURCE_DIR" -name "postgresql*.sql" -print | cut -d'/' -f2-); do
    dest_file="${DEST_DIR}/${i}"
    mkdir -p "$(dirname "${dest_file}")"
    # in case of errors, remove file
    if ! get_translated_sql_file "${SOURCE_DIR}/${i}" >"${dest_file}"; then
      rm -f "${dest_file}"
    fi
  done
}

apply_dbconfig_settings()
{
  # this function is only useful on Debian Linux based distributions
  # as dbconfig-common is only available there

  # check if dbconfig configuration file exists
  [ -r "/etc/dbconfig-common/bareos-database-common.conf" ] || return 0
  . /etc/dbconfig-common/bareos-database-common.conf

  # check if dbconfig is enabled
  [ "${dbc_install}" = "true" ] && [ "${dbc_upgrade}" = "true" ] || return 0

  # Set the address only if not localhost, so local connection use socket
  if [ -n "${dbc_dbserver}" ] && [ "${dbc_dbserver}" != "localhost" ]; then
    set_config_param "bareos-dir" "Catalog" "MyCatalog" "dbaddress" "${dbc_dbserver}"
  fi

  if [ -n "${dbc_dbuser}" ]; then
    set_config_param "bareos-dir" "Catalog" "MyCatalog" "dbuser" "\"${dbc_dbuser}\""
  fi

  if [ -n "${dbc_dbname}" ]; then
    set_config_param "bareos-dir" "Catalog" "MyCatalog" "dbname" "\"${dbc_dbname}\""
  fi

  if [ "${dbc_authmethod_user}" != "ident" ] && [ "${dbc_dbpass}" ]; then
    set_config_param "bareos-dir" "Catalog" "MyCatalog" "dbpassword" "\"${dbc_dbpass}\""
  fi
}

get_local_hostname()
{
  # put actual short hostname in configuration files
  if [ -n "$(command -v uname)" ]; then
    hname=$(uname -n)
  fi
  if [ -z "${hname}" ] && [ -f '/etc/hostname' ]; then
    hname="$(cut -d '.' -f1 '/etc/hostname')"
  fi
  if [ -z "${hname}" ]; then
    # set to "localhost"
    hname='localhost'
  fi
  echo "${hname}"
}

replace()
{
  if [ $# -ne 2 ]; then
    return 1
  fi

  SEARCH="$1"
  REPLACE="$2"

  grep -l "${SEARCH}" \
    "${BAREOS_CONFIG_DIR}"/*.conf "${BAREOS_CONFIG_DIR}"/bareos-*.d/*/*.conf "${BAREOS_CONFIG_DIR}"/tray-monitor.d/*/*.conf \
    "${BAREOS_CONFIG_DIR}"/*.conf.example "${BAREOS_CONFIG_DIR}"/bareos-*.d/*/*.conf.example "${BAREOS_CONFIG_DIR}"/tray-monitor.d/*/*.conf.example \
    2>/dev/null | while read -r file; do
    if [ -f "${file}" ]; then
      if grep -q "${SEARCH}" "$file"; then
        # do a log-message only if file will be changed, not always
        if echo "${SEARCH}" | grep -q "_PASSWORD_"; then
          info "replacing '${SEARCH}' in ${file}"
        else
          info "replacing '${SEARCH}' with '${REPLACE}' in ${file}"
        fi
        # trying to refactor in place as a variable will lead to errors.
        if [ "$os_type" = "Darwin" ] || [ "$os_type" = "FreeBSD" ]; then
          sed -i "" "s#${SEARCH}#${REPLACE}#g" "${file}"
        else
          sed --in-place "s#${SEARCH}#${REPLACE}#g" "${file}"
        fi
      fi
    fi
  done
  return 0
}

initialize_local_hostname()
{
  #
  # Replace all XXX_REPLACE_WITH_LOCAL_HOSTNAME by the local hostname.
  #
  hname=$(get_local_hostname)
  replace "XXX_REPLACE_WITH_LOCAL_HOSTNAME_XXX" "${hname}"
}

initialize_passwords()
{
  #
  # See if we need to generate a set of random passwords.
  #
  if [ ! -f "${BAREOS_CONFIG_DIR}"/.rndpwd ]; then
    for string in ${PASSWORD_SUBST}; do
      pass="$(RANDFILE=/dev/urandom openssl rand -base64 33)"
      echo "${string}=${pass}" >>"${BAREOS_CONFIG_DIR}"/.rndpwd
    done
    chmod 400 "${BAREOS_CONFIG_DIR}"/.rndpwd
  fi

  # Source the passwords
  . "${BAREOS_CONFIG_DIR}"/.rndpwd

  for string in ${PASSWORD_SUBST}; do
    eval "pass=\${$string}"
    if [ -n "${pass}" ]; then
      replace "${string}" "${pass}"
    fi
  done
}

init()
{
  initialize_local_hostname
  initialize_passwords
}
