#!/bin/sh
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2018-2025, Christer Edwards <christer.edwards@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

. /usr/local/share/bastille/common.sh


usage() {
    error_notify "Usage: bastille config [option(s)] TARGET [get|(set|add)|remove] PROPERTY [VALUE]"
    cat << EOF

    Options:

    -x | --debug          Enable debug mode.

EOF
    exit 1
}

# we need jail(8) to parse the config file so it can expand variables etc
print_jail_conf() {

    # we need to pass a literal \n to jail to get each parameter on its own
    # line
    jail -f "$1" -e '
'
}

# Handle options.
while [ "$#" -gt 0 ]; do
    case "${1}" in
        -h|--help|help)
            usage
            ;;
        -x|--debug)
            enable_debug
            shift
            ;;
        -*)
            error_notify "[ERROR]: Unknown Option: \"${1}\""
            usage
            ;;
        *)
            break
            ;;
    esac
done

if [ "$#" -lt 1 ] || [ "$#" -gt 4 ]; then
    usage
fi

bastille_root_check

TARGET="${1}"
ACTION="${2}"
BASTILLE_PROPERTY=0
shift 2

set_target "${TARGET}"

case "${ACTION}" in
    get|remove)
        if [ "$#" -ne 1 ]; then
            error_exit "[ERROR]: Too many parameters for [get|remove] operation."
        fi
        ;;
    add|set)
        ;;
    *)
        error_exit "[ERROR]: Only (add|set), get and remove are supported."
        ;;
esac

if [ "${ACTION}" = "add" ]; then
    ACTION="set"
fi

PROPERTY="${1}"
shift
VALUE="$@"

case "${PROPERTY}" in
    boot|depend|depends|prio|priority)
        BASTILLE_PROPERTY=1
        ;;
esac

# we need jail(8) to parse the config file so it can expand variables etc
print_jail_conf() {

    # we need to pass a literal \n to jail to get each parameter on its own
    # line
    jail -f "${1}" -e '
'
}

for _jail in ${JAILS}; do

    # Backwards compatibility for specifying only an IP with ip[4|6].addr
    if [ "${ACTION}" = "set" ] && [ "${PROPERTY}" = "ip4.addr" ]; then
        if ! echo "${VALUE}" | grep -q "|"; then
            VALUE="$(bastille config ${_jail} get ip4.addr | awk -F"|" '{print $1}')|${VALUE}"
        fi
    elif [ "${ACTION}" = "set" ] && [ "${PROPERTY}" = "ip6.addr" ]; then
        if ! echo "${VALUE}" | grep -q "|"; then
            VALUE="$(bastille config ${_jail} get ip6.addr | awk -F"|" '{print $1}')|${VALUE}"
        fi
    fi

    # Handle Bastille specific properties
    # Currently only 'depend' 'priority' and 'boot'
    if [ "${PROPERTY}" = "priority" ] || [ "${PROPERTY}" = "prio" ]; then

        PROPERTY="priority"
        FILE="${bastille_jailsdir}/${_jail}/settings.conf"

        if [ "${ACTION}" = "set" ]; then
            if echo "${VALUE}" | grep -Eq '^[0-9]+$'; then
                sysrc -f "${FILE}" "${PROPERTY}=${VALUE}"
            else
                error_exit "Priority value must be a number."
            fi
        elif [ "${ACTION}" = "remove" ]; then
            error_exit "[ERROR]: Cannot remove the 'priority' property."
        elif [ "${ACTION}" = "get" ]; then
            sysrc -f "${FILE}" -n "${PROPERTY}"
        fi

    # Boot property
    elif [ "${PROPERTY}" = "boot" ]; then

        FILE="${bastille_jailsdir}/${_jail}/settings.conf"

        if [ "${ACTION}" = "set" ]; then
            if [ "${VALUE}" = "on" ] || [ "${VALUE}" = "off" ]; then
                sysrc -f "${FILE}" "${PROPERTY}=${VALUE}"
            else
                error_exit "Boot value must be 'on' or 'off'."
            fi
        elif [ "${ACTION}" = "remove" ]; then
            error_exit "[ERROR]: Cannot remove the 'boot' property."
        elif [ "${ACTION}" = "get" ]; then
            sysrc -f "${FILE}" -n "${PROPERTY}"
        fi

    # Depend property
    elif [ "${PROPERTY}" = "depend" ] || [ "${PROPERTY}" = "depends" ]; then

        PROPERTY="depend"
        FILE="${bastille_jailsdir}/${_jail}/settings.conf"

        if [ "${ACTION}" = "set" ]; then

            if [ -z "${VALUE}" ]; then
                error_exit "[ERROR]: Adding a jail to the 'depend' property requires a TARGET."
            else
                set_target "${VALUE}"
            fi

            info "\n[${_jail}]:"

            sysrc -f "${FILE}" "${PROPERTY}+=${JAILS}"

        elif [ "${ACTION}" = "remove" ]; then

            if [ -z "${VALUE}" ]; then
                error_exit "[ERROR]: Removing a jail from the 'depend' property requires a TARGET."
            else
                set_target "${VALUE}"
            fi

            info "\n[${_jail}]:"

            sysrc -f "${FILE}" "${PROPERTY}-=${JAILS}"

        elif [ "${ACTION}" = "get" ]; then

            sysrc -f "${FILE}" -n "${PROPERTY}"

        fi
    else
        FILE="${bastille_jailsdir}/${_jail}/jail.conf"
        if [ ! -f "${FILE}" ]; then
            error_notify "jail.conf does not exist for jail: ${_jail}"
            continue
        fi
        if [ "${ACTION}" = 'get' ]; then
            _output=$(
                print_jail_conf "${FILE}" | awk -F= -v property="${PROPERTY}" '
                    $1 == property {
                        # note that we have found the property
                        found = 1;
                        # check if there is a value for this property
                        if (NF == 2) {
                            # remove any quotes surrounding the string
                            #sub(",[^|]*\\|", ",", $2);
                            sub(/^"/, "", $2);
                            sub(/"$/, "", $2);
                            print $2;
                        } else {
                            # no value, just the property name
                            print "enabled";
                        }
                        exit 0;
                    }
                    END {
                        # if we have not found anything we need to print a special
                        # string
                        if (! found) {
                            print("not set");
                            #  let the caller know that this is a warn condition
                            exit(120);
                        }
                    }'
                )
            # check if our output is a warning or regular
            if [ $? -eq 120 ]; then
                warn "${_output}"
            else
                echo "${_output}"
            fi
        elif [ "${ACTION}" = "remove" ]; then
            if [ "$(bastille config ${_jail} get ${PROPERTY})" != "not set" ]; then

                info "\n[${_jail}]:"

                sed -i '' "/.*${PROPERTY}.*/d" "${FILE}"

                echo "Property removed: ${PROPERTY}"

            else
                error_exit "[ERROR]: Value not present in jail.conf: ${PROPERTY}"
            fi
        else # Setting the value. -- cwells
            if [ -n "${VALUE}" ]; then
                VALUE=$(echo "${VALUE}" | sed 's/\//\\\//g')
                if echo "${VALUE}" | grep ' ' > /dev/null 2>&1; then # Contains a space, so wrap in quotes. -- cwells
                    VALUE="'${VALUE}'"
                fi
                LINE="  ${PROPERTY} = ${VALUE};"
            else
                LINE="  ${PROPERTY};"
            fi

            # add the value to the config file, replacing any existing value or, if
            # there is none, at the end
            #
            # awk doesn't have "inplace" editing so we use a temp file
            _tmpfile=$(mktemp) || error_exit "unable to set because mktemp failed"
            cp "${FILE}" "${_tmpfile}" && \
            awk -F= -v line="${LINE}" -v property="${PROPERTY}" '
                BEGIN {
                    # build RE as string as we can not expand vars in RE literals
                    prop_re = "^[[:space:]]*" property "[[:space:]]*;?$";
                }
                $1 ~ prop_re && !found {
                    # we already have an entry in the config for this property so
                    # we need to substitute our line here rather than keep the
                    # existing line
                    print(line);
                    # note we have already found the property
                    found = 1;
                    # move onto the next line
                    next;
                }
                $1 == "}" {
                    # reached the end of the stanza so if we have not already
                    # added our line we need to do so now
                    if (! found) {
                        print(line);
                    }
                }
                {
                    # print each uninteresting line unchanged
                    print;
                }
            ' "${_tmpfile}" > "${FILE}"
            rm "${_tmpfile}"
        fi
    fi

done

# Only display this message once at the end (not for every jail). -- cwells
if { [ "${ACTION}" = "set" ] || [ "${ACTION}" = "remove" ]; } && [ "${BASTILLE_PROPERTY}" -eq 0 ]; then
    info "A restart is required for the changes to be applied. See 'bastille restart'."
fi

exit 0
