summaryrefslogtreecommitdiffstats
path: root/src/bin/admin/kea-admin.in
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/admin/kea-admin.in')
-rw-r--r--src/bin/admin/kea-admin.in1057
1 files changed, 1057 insertions, 0 deletions
diff --git a/src/bin/admin/kea-admin.in b/src/bin/admin/kea-admin.in
new file mode 100644
index 0000000..b68baa8
--- /dev/null
+++ b/src/bin/admin/kea-admin.in
@@ -0,0 +1,1057 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This is kea-admin script that conducts administrative tasks on the Kea
+# installation. Currently supported operations are:
+#
+# - database init
+# - database version check
+# - database version upgrade
+# - lease database dump
+# - lease upload to the database
+# - lease database recount
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# shellcheck disable=SC2086
+# SC2086: Double quote to prevent globbing and word splitting.
+# Reason for disable: explicitly don't quote extra_arguments so it is
+# considered multiple arguments instead of one.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Shell ${variables} derived from autoconf @variables@. Some depend on others, so mind the order.
+prefix="@prefix@"
+export prefix
+exec_prefix="@exec_prefix@"
+export exec_prefix
+SCRIPTS_DIR_DEFAULT="@datarootdir@/@PACKAGE@/scripts"
+scripts_dir="${SCRIPTS_DIR_DEFAULT}"
+VERSION="@PACKAGE_VERSION@"
+
+assume_yes=0
+
+# lease dump parameters
+dhcp_version=0
+dump_file=""
+dump_qry=""
+
+# Include the installed admin-utils.sh if available. Fallback to sources otherwise.
+if test -d "@datarootdir@/@PACKAGE_NAME@"; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_srcdir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Find the installed kea-lfc if available. Fallback to sources otherwise.
+if test -d "@sbindir@"; then
+ kea_lfc="@sbindir@/kea-lfc"
+else
+ kea_lfc="@abs_top_srcdir@/src/bin/lfc/kea-lfc"
+fi
+
+# Prints out usage version.
+usage() {
+ printf \
+'
+kea-admin %s
+
+This is a kea-admin script that conducts administrative tasks on
+the Kea installation.
+
+Usage: %s COMMAND BACKEND [parameters]
+
+COMMAND: Currently supported operations are:
+
+ - db-init: Initializes new database. Useful for first time installation.
+ - db-version: Checks version of the existing database schema. Useful
+ - for checking database version when preparing for an upgrade.
+ - db-upgrade: Upgrades your database schema.
+ - lease-dump: Dumps current leases to a memfile-ready CSV file.
+ - lease-upload: Uploads leases from a CSV file to the database.
+ - stats-recount: Recounts lease statistics.
+
+BACKEND - one of the supported backends: memfile|mysql|pgsql
+
+PARAMETERS: Parameters are optional in general, but may be required
+ for specific operations.
+ -h or --host hostname - specifies a hostname of a database to connect to
+ -P or --port port - specifies the TCP port to use for the database connection
+ -u or --user name - specifies username when connecting to a database
+ -p or --password [password] - specifies a password for the database connection;
+ if omitted from the command line,
+ then the user will be prompted for a password
+ -n or --name database - specifies a database name to connect to
+ -d or --directory - path to upgrade scripts (default: %s)
+ -v or --version - print kea-admin version and quit.
+ -x or --extra - specifies extra argument(s) to pass to the database command
+
+ Parameters specific to lease-dump, lease-upload:
+ -4 to dump IPv4 leases to file
+ -6 to dump IPv6 leases to file
+ -i or --input to specify the name of file from which leases will be uploaded
+ -o or --output to specify the name of file to which leases will be dumped
+ -y or --yes - assume yes on overwriting temporary files
+' "${VERSION}" "${0}" "${SCRIPTS_DIR_DEFAULT}"
+}
+
+### Logging functions ###
+
+# Logs message at the error level.
+# Takes one parameter that is printed as is.
+log_error() {
+ printf "ERROR/kea-admin: %s\n" "${1}"
+}
+
+# Logs message at the warning level.
+# Takes one parameter that is printed as is.
+log_warning() {
+ printf "WARNING/kea-admin: %s\n" "${1}"
+}
+
+# Logs message at the info level.
+# Takes one parameter that is printed as is.
+log_info() {
+ printf "INFO/kea-admin: %s\n" "${1}"
+}
+
+### Convenience functions ###
+
+# Checks if the value is in the list. An example usage of this function
+# is to determine whether the kea-admin command belongs to the list of
+# supported commands.
+is_in_list() {
+ local member="${1-}" # Value to be checked
+ local list="${2-}" # Comma separated list of items
+ _inlist=0 # Return value: 0 if not in list, 1 otherwise.
+ if [ -z "${member}" ]; then
+ log_error "missing member (need to specify a string as first param)"
+ fi
+ # Iterate over all items on the list and compare with the member.
+ # If they match, return, otherwise log error and exit.
+ for item in ${list}
+ do
+ if [ "${item}" = "${member}" ]; then
+ _inlist=1
+ return
+ fi
+ done
+}
+
+### Functions that implement database initialization commands
+
+memfile_init() {
+ # Useless as Kea converts CSV versions at startup.
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+# Validates that the MySQL db_users's permissions are sufficient to
+# create the schema.
+mysql_can_create() {
+
+ # Let's grab the version for possible debugging issues. It also
+ # determines basic functional access to db.
+ run_command \
+ mysql_execute "select @@global.version;"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create: get MySQL version failed, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # shellcheck disable=SC2153
+ # SC2153: Possible misspelling: ... may not be assigned, but ... is.
+ # Reason for disable: OUTPUT is assigned in run_command.
+ printf "MySQL Version is: %s\n" "${OUTPUT}"
+
+ # SQL to drop our test table and trigger
+ cleanup_sql="DROP TABLE IF EXISTS kea_dummy_table; DROP PROCEDURE IF EXISTS kea_dummy_trigger;"
+
+ # SQL to create our test table
+ table_sql="CREATE TABLE kea_dummy_table(dummy INT UNSIGNED PRIMARY KEY NOT NULL);"
+
+ # SQL to create our test trigger
+ trigger_sql="\
+CREATE TRIGGER kea_dummy_trigger BEFORE insert ON kea_dummy_table FOR EACH ROW\n \
+BEGIN\n \
+END;"
+
+ # Let's clean up just in case.
+ run_command \
+ mysql_execute "$cleanup_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot run pre cleanup, mysql status = ${EXIT_CODE}"
+ exit 1;
+ fi
+
+ # Now make the dummy table.
+ perms_ok=1
+ run_command \
+ mysql_execute "$table_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot create table, check user permissions, mysql status = ${EXIT_CODE}"
+ perms_ok=0;
+ else
+ # Now attempt to make trigger
+ run_command \
+ mysql_execute "$trigger_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot trigger, check user permissions, mysql status = ${EXIT_CODE}"
+ perms_ok=0;
+ fi
+ fi
+
+ # Try to cleanup no matter what happened above
+ run_command \
+ mysql_execute "$cleanup_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot run post cleanup, mysql status = ${EXIT_CODE}"
+ exit 1;
+ fi
+
+ if [ $perms_ok -ne 1 ]
+ then
+ log_error "Create failed, the user, $db_user, has insufficient privileges."
+ exit 1;
+ fi
+}
+
+# Initializes a new, empty MySQL database.
+# It essentially calls scripts/mysql/dhcpdb_create.mysql script, with
+# some extra sanity checks. It will refuse to use it if there are any
+# existing tables. It's better safe than sorry.
+mysql_init() {
+ printf 'Checking if there is a database initialized already...\n'
+
+ # Let's try to count the number of tables. Anything above 0 means that there
+ # is some database in place. If there is anything, we abort. Note that
+ # mysql may spit out connection or access errors to stderr, we ignore those.
+ # We should not hide them as they may give hints to user what is wrong with
+ # his setup.
+ run_command \
+ mysql_execute "SHOW TABLES;"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_init table query failed, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ count=$(printf '%s' "${OUTPUT}" | wc -w)
+ if [ "${count}" -gt 0 ]; then
+ # Let's start with a new line. mysql could have printed something out.
+ printf '\n'
+ log_error "Expected empty database ${db_name}. Aborting, the following tables are present:
+ ${OUTPUT}"
+ exit 1
+ fi
+
+ # Beginning with MySQL 8.0, the db user needs additional settings or SUPER
+ # privileges to create triggers and or functions. Call mysql_can_create to find
+ # out if we're good to go. If not, it will exit.
+ printf "Verifying create permissions for %s\n" "$db_user"
+ mysql_can_create
+
+ printf "Initializing database using script %s\n" $scripts_dir/mysql/dhcpdb_create.mysql
+ mysql -B --host="${db_host}" --user="${db_user}" \
+ --password="${db_password}" \
+ "${db_name}" ${extra_arguments} < "${scripts_dir}/mysql/dhcpdb_create.mysql"
+
+ printf "mysql returned status code %s\n" "${EXIT_CODE}"
+
+ if [ "${EXIT_CODE}" -eq 0 ]; then
+ printf "Database version reported after initialization: "
+ checked_mysql_version
+ printf '\n'
+ fi
+
+ exit "${EXIT_CODE}"
+}
+
+pgsql_init() {
+ printf 'Checking if there is a database initialized already...\n'
+
+ # Let's try to count the number of tables. Anything above 0 means that there
+ # is some database in place. If there is anything, we abort.
+ run_command \
+ pgsql_execute "\d"
+ if [ "${EXIT_CODE}" -ne 0 ]; then
+ log_error "pgsql_init: table query failed, status code: ${EXIT_CODE}?"
+ exit 1
+ fi
+
+ count=$(printf '%s' "${OUTPUT}" | wc -w)
+ if [ "${count}" -gt 0 ]; then
+ # Let's start with a new line. pgsql could have printed something out.
+ printf '\n'
+ log_error "Expected empty database ${db_name}. Aborting, the following tables are present:
+ ${OUTPUT}"
+ exit 2
+ fi
+
+ init_script="$scripts_dir/pgsql/dhcpdb_create.pgsql"
+ printf "Initializing database using script %s\n" $init_script
+ run_command \
+ pgsql_execute_script $init_script
+ if [ "${EXIT_CODE}" -ne 0 ]; then
+ log_error "Database initialization failed, status code: ${EXIT_CODE}?"
+ exit 1
+ fi
+
+ version=$(checked_pgsql_version)
+ printf "Database version reported after initialization: %s\n" "$version"
+ exit 0
+}
+
+### Functions that implement database version checking commands
+memfile_version() {
+ # @todo Implement this?
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+### Functions used for upgrade
+memfile_upgrade() {
+ # Useless as Kea converts CSV versions at startup.
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+# Upgrades existing MySQL database installation. The idea is that
+# it will go over all upgrade scripts from (prefix)/share/kea/scripts/mysql
+# and run them one by one. They will be named properly, so they will
+# be run in order.
+#
+# This function prints version before and after upgrade.
+mysql_upgrade() {
+
+ printf "Database version reported before upgrade: "
+ checked_mysql_version
+ printf '\n'
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${scripts_dir}/mysql ]; then
+ log_error "Invalid scripts directory: ${scripts_dir}/mysql"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${scripts_dir}/mysql" -name 'upgrade*.sh' -type f | wc -l)
+ if [ "$num_files" -eq 0 ]; then
+ log_error "No scripts in ${scripts_dir}/mysql or the directory is not readable or does not have any upgrade* scripts."
+ exit 1
+ fi
+
+ # Beginning with MySQL 8.0, the db user needs additional settings or SUPER
+ # privileges to create triggers and or functions. Call mysql_can_create to find
+ # out if we're good to go. If not, it will exit.
+ printf "Verifying upgrade permissions for %s\n" "$db_user"
+ mysql_can_create
+
+ for script in "${scripts_dir}"/mysql/upgrade*.sh
+ do
+ echo "Processing $script file..."
+ "${script}" --host="${db_host}" --user="${db_user}" \
+ --password="${db_password}" "${db_name}" ${extra_arguments}
+ done
+
+ printf "Database version reported after upgrade: "
+ checked_mysql_version
+ printf '\n'
+}
+
+pgsql_upgrade() {
+ version=$(checked_pgsql_version)
+ printf "Database version reported before upgrade: %s\n" "$version"
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${scripts_dir}/pgsql ]; then
+ log_error "Invalid scripts directory: ${scripts_dir}/pgsql"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${scripts_dir}/pgsql" -name 'upgrade*.sh' -type f | wc -l)
+ if [ "$num_files" -eq 0 ]; then
+ log_error "No scripts in ${scripts_dir}/pgsql or the directory is not readable or does not have any upgrade* scripts."
+ exit 1
+ fi
+
+ # Postgres psql does not accept pw on command line, but can do it
+ # thru an env
+ export PGPASSWORD=$db_password
+
+ for script in "${scripts_dir}"/pgsql/upgrade*.sh
+ do
+ echo "Processing $script file..."
+ "${script}" -U "${db_user}" -h "${db_host}" \
+ -d "${db_name}" ${extra_arguments}
+ done
+
+ version=$(checked_pgsql_version)
+ printf "Database version reported after upgrade: %s\n" "$version"
+ exit 0
+}
+
+# Remove a file if it exists
+remove_file () {
+ local file="${1}"
+ if [ -e "${file}" ]
+ then
+ log_info "Removing file ${file}..."
+ rm -f "${file}"
+ fi
+}
+
+# Utility function which tests if the given file exists and
+# if so notifies the user and provides them the opportunity
+# to abort the current command.
+check_file_overwrite () {
+ local file="${1}"
+ if [ $assume_yes -eq 1 ]
+ then
+ remove_file "${file}"
+ elif [ -e "${file}" ]
+ then
+ echo "Output file, $file, exists and will be overwritten."
+ echo "Do you wish to continue? (y/N)"
+
+ # Ask for an answer only on an interactive shell to prevent blocking in
+ # automated or non-interactive scenarios where the answer defaults to no.
+ if test -t 0; then
+ read -r ans
+ else
+ log_warning 'Non-interactive tty detected. Assuming no.'
+ ans='N'
+ fi
+
+ if [ "${ans}" != "y" ]
+ then
+ echo "$command aborted by user."
+ exit 1
+ fi
+ fi
+}
+
+### Functions used for dump
+
+# Sets the global variable, dump_qry, to the schema-version specific
+# SQL text needed to dump the lease data for the current backend
+# and protocol
+get_dump_query() {
+ local version="${1}"
+
+ case ${backend} in
+ mysql)
+ invoke="call"
+ ;;
+ pgsql)
+ invoke="select * from"
+ ;;
+ *)
+ log_error "unsupported backend ${backend}"
+ usage
+ exit 1
+ ;;
+ esac
+
+ dump_qry="${invoke} lease${dhcp_version}DumpHeader();${invoke} lease${dhcp_version}DumpData();";
+}
+
+memfile_dump() {
+ log_error "lease-dump is not supported for memfile"
+ exit 1
+}
+
+mysql_dump() {
+
+ # Check the lease type was given
+ if [ ${dhcp_version} -eq 0 ]; then
+ log_error "lease-dump: lease type ( -4 or -6 ) needs to be specified"
+ usage
+ exit 1
+ fi
+
+ # get the correct dump query
+ run_command \
+ mysql_version
+ version="${OUTPUT}"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "lease-dump: mysql_version failed, exit code ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # Fetch the correct SQL text. Note this function will exit
+ # if it fails.
+ get_dump_query "$version"
+
+ # Make sure they specified a file
+ if [ "$dump_file" = "" ]; then
+ log_error "you must specify an output file for lease-dump"
+ usage
+ exit 1
+
+ fi
+
+ # If output file exists, notify user, allow them a chance to bail
+ check_file_overwrite "$dump_file"
+
+ # Check the temp file too
+ tmp_file="/tmp/$(basename "${dump_file}").tmp"
+ check_file_overwrite $tmp_file
+
+ # Run the sql to output tab-delimited lease data to a temp file.
+ # By using a temp file we can check for MySQL errors before using
+ # 'tr' to translate tabs to commas. We do not use MySQL's output
+ # to file as that requires linux superuser privileges to execute
+ # the select.
+ if ! mysql_execute "${dump_qry}" > $tmp_file; then
+ log_error "lease-dump: mysql_execute failed, exit code ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # Now translate tabs to commas.
+ if ! tr '\t' ',' < "${tmp_file}" > "${dump_file}"; then
+ log_error "lease-dump: reformatting failed";
+ exit 1
+ fi
+
+ # Clean up the temporary file.
+ rm -f "${tmp_file}"
+ log_info "Removed temporary file ${tmp_file}."
+
+ log_info "Successfully dumped lease${dhcp_version} to ${dump_file}."
+ exit 0
+}
+
+### Functions used for dump
+pgsql_dump() {
+ # Check the lease type was given
+ if [ ${dhcp_version} -eq 0 ]; then
+ log_error "lease-dump: lease type ( -4 or -6 ) needs to be specified"
+ usage
+ exit 1
+ fi
+
+ version=$(pgsql_version)
+ get_dump_query "$version"
+
+ # Make sure they specified a file
+ if [ "$dump_file" = "" ]; then
+ log_error "you must specify an output file for lease-dump"
+ usage
+ exit 1
+
+ fi
+
+ # If output file exists, notify user, allow them a chance to bail
+ check_file_overwrite "$dump_file"
+
+ # psql does not accept password as a parameter but will look in the environment
+ export PGPASSWORD=$db_password
+
+ # Call psql and redirect output to the dump file. We don't use psql "to csv"
+ # as it can only be run as db superuser. Check for errors.
+ if ! (
+ echo "${dump_qry}" | \
+ psql --set ON_ERROR_STOP=1 -t -h "${db_host}" -q --user="${db_user}" \
+ --dbname="${db_name}" -w --no-align --field-separator=',' \
+ ${extra_arguments} > "${dump_file}"
+ ); then
+ log_error "lease-dump: psql call failed, exit code: ${?}"
+ exit 1
+ fi
+
+ echo lease${dhcp_version} successfully dumped to "${dump_file}"
+ exit 0
+}
+
+######################## functions used in lease-upload ########################
+
+# Finds the position of a column by name, starting with 1.
+get_column_position() {
+ local column_name="${1}"; shift
+
+ # Look only in the header, count the number of commas up to the column.
+ position=$(head -n 1 "${input_file}" | grep -Eo ",|${column_name}" | uniq -c | \
+ head -n 1 | grep ',' | tr -s ' ' | cut -d ' ' -f 2)
+
+ if test -z "${position}"; then
+ # If no commas, that means position 1.
+ printf '1'
+ else
+ # Else increment, so that it starts at 1.
+ printf '%s' "$((position + 1))"
+ fi
+}
+
+# Adds quotes around values at given positions starting with 1 in a CSV line.
+stringify_positions_in_line() {
+ local positions="${1}"; shift
+ local line="${1}"; shift
+
+ i=1
+ output=
+ for p in ${positions}; do
+ # Get everything up to position p.
+ if test "${i}" -lt "${p}"; then
+ up_until_p=$(printf '%s' "${line}" | cut -d ',' -f "${i}-$((p - 1))")
+ else
+ up_until_p=''
+ fi
+
+ # Get value at position p.
+ p_word=$(printf '%s' "${line}" | cut -d ',' -f "${p}")
+
+ # Add comma unless we're doing the first append.
+ if test "${i}" != 1; then
+ output="${output},"
+ fi
+
+ # Append everything up to position p.
+ output="${output}${up_until_p}"
+
+ # Add comma if we're not stringifying position 1 and if there is
+ # anything up to position p. In the second case, the comma was already
+ # added at a previous step.
+ if test "${p}" != 1 && test -n "${up_until_p}"; then
+ output="${output},"
+ fi
+
+ # Add value at position p.
+ output="${output}'${p_word}'"
+
+ # Skip position p when getting the values between positions next time.
+ i=$((p + 1))
+ done
+
+ # The last position might be somewhere in the middle, so add everything
+ # until the end.
+ the_rest=$(printf '%s' "${line}" | cut -d ',' -f ${i}-)
+
+ # Add comma unless we're adding the whole line or nothing.
+ if test "${i}" != 1 && test -n "${the_rest}"; then
+ output="${output},"
+ fi
+
+ # Append.
+ output="${output}${the_rest}"
+
+ # Print back to the caller.
+ printf '%s' "${output}"
+}
+
+# Entry point for the lease-upload command.
+lease_upload() {
+ # Check that an input file was specified.
+ if test -z "${input_file-}"; then
+ log_error 'you must specify an input file with -i or --input for lease-upload'
+ usage
+ exit 1
+ fi
+
+ # Check that the input file has at least one row of values.
+ input_file_line_length=$(wc -l < "${input_file}")
+ if test "${input_file_line_length}" -le 1; then
+ log_error 'CSV file has no leases'
+ exit 1
+ fi
+
+ # Invoke LFC on the input file.
+ log_info "Looking at ${input_file_line_length} lines of CSV in ${input_file}..."
+ cleaned_up_csv="/tmp/$(basename "${input_file}").tmp"
+ check_file_overwrite "${cleaned_up_csv}"
+ cp "${input_file}" "${cleaned_up_csv}"
+ "${kea_lfc}" "-${dhcp_version}" -x "${cleaned_up_csv}" \
+ -i "${cleaned_up_csv}.1" -o "${cleaned_up_csv}.output" \
+ -f "${cleaned_up_csv}.completed" -p "${cleaned_up_csv}.pid" \
+ -cignored-path
+ cleaned_up_csv_line_length=$(wc -l < "${cleaned_up_csv}")
+ log_info "Reduced to ${cleaned_up_csv_line_length} lines in ${cleaned_up_csv}."
+
+ # Determine the columns whose values need to be stringified to avoid syntax
+ # errors in the MySQL client. These are columns which are VARCHARs or need
+ # to be further processed by a procedure.
+ if test "${dhcp_version}" = '4'; then
+ string_columns='address hwaddr client_id hostname user_context'
+ elif test "${dhcp_version}" = '6'; then
+ string_columns='address duid hostname hwaddr user_context'
+ else
+ log_error "lease-upload: lease type -4 or -6 needs to be specified"
+ usage
+ exit 1
+ fi
+
+ # Get positions of string columns.
+ string_positions=
+ for i in ${string_columns}; do
+ string_positions="${string_positions} $(get_column_position "${i}")"
+ done
+
+ # Construct the SQL insert statements.
+ header_parsed=false
+ sql_statement='START TRANSACTION;'
+ while read -r line; do
+ if "${header_parsed}"; then
+ line=$(stringify_positions_in_line "${string_positions}" "${line}")
+ if test "${backend}" = 'mysql'; then
+ sql_statement="${sql_statement} CALL lease${dhcp_version}Upload(${line}); "
+ elif test "${backend}" = 'pgsql'; then
+ sql_statement="${sql_statement} SELECT lease${dhcp_version}Upload(${line}); "
+ else
+ log_error "lease-upload not implemented for ${backend}"
+ exit 1
+ fi
+ else
+ header_parsed=true
+ fi
+ done < "${cleaned_up_csv}"
+ sql_statement="${sql_statement} COMMIT;"
+
+ # Execute the SQL insert statements.
+ if test "${backend}" = 'mysql'; then
+ output="$(mysql_execute "${sql_statement}")"
+ elif test "${backend}" = 'pgsql'; then
+ output="$(pgsql_execute "${sql_statement}")"
+ else
+ log_error "lease-upload not implemented for ${backend}"
+ exit 1
+ fi
+
+ # Clean up the temporary CSV.
+ rm -f "${cleaned_up_csv}"
+ log_info "Removed temporary file ${cleaned_up_csv}."
+
+ # Print a confirmation message.
+ log_info "Successfully updated table lease${dhcp_version}."
+}
+
+### Functions used for recounting statistics
+mysql_recount() {
+ printf "Recount lease statistics from database\n"
+
+ run_command \
+ mysql_execute "$_RECOUNT4_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql failed to recount IPv4 leases, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ run_command \
+ mysql_execute "$_RECOUNT6_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql failed to recount IPv6 leases, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+}
+
+pgsql_recount() {
+ printf "Recount lease statistics from database\n"
+
+ run_command \
+ pgsql_execute "$_RECOUNT4_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "pgsql failed to recount IPv4 leases, pgsql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ run_command \
+ pgsql_execute "$_RECOUNT6_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "pgsql failed to recount IPv6 leases, pgsql status = ${EXIT_CODE}"
+ exit 1
+ fi
+}
+
+### Script starts here ###
+
+# First, find what the command is
+command=${1-}
+if [ -z "${command}" ]; then
+ log_error "missing command"
+ usage
+ exit 1
+fi
+
+# Check if this is a simple question about version.
+if test "${command}" = "-v" || test "${command}" = "--version" ; then
+ echo "${VERSION}"
+ exit 0
+fi
+
+is_in_list "${command}" "db-init db-version db-upgrade lease-dump lease-upload stats-recount"
+if [ "${_inlist}" -eq 0 ]; then
+ log_error "invalid command: ${command}"
+ usage
+ exit 1
+fi
+shift
+
+# Second, check what's the backend
+backend=${1-}
+if [ -z "${backend}" ]; then
+ log_error "missing backend"
+ usage
+ exit 1
+fi
+is_in_list "${backend}" "memfile mysql pgsql"
+if [ "${_inlist}" -eq 0 ]; then
+ log_error "invalid backend: ${backend}"
+ exit 1
+fi
+shift
+
+# Ok, let's process parameters (if there are any)
+while test "${#}" -gt 0
+do
+ option=${1}
+ case ${option} in
+ # Specify database host
+ -h|--host)
+ shift
+ db_host=${1-}
+ if [ -z "${db_host}" ]; then
+ log_error "-h or --host requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # Specify database port
+ -P|--port)
+ shift
+ if test -z "${1+x}"; then
+ log_error '-P or --port requires a parameter'
+ usage
+ exit 1
+ fi
+ db_port=${1}
+ export db_port_full_parameter="--port=${db_port}"
+ ;;
+ # Specify database user
+ -u|--user)
+ shift
+ db_user=${1-}
+ if [ -z "${db_user}" ]; then
+ log_error "-u or --user requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # Specify database password
+ -p|--password)
+ password_parameter_passed=true
+ # If there is at least one more parameter following...
+ if test "${#}" -gt 1; then
+ # Then take it as password.
+ shift
+ db_password=${1}
+ else
+ # If it's an interactive shell...
+ if test -t 0; then
+ # Read from standard input while hiding feedback to the terminal.
+ printf 'Password: '
+ stty -echo
+ read -r db_password
+ stty echo
+ printf '\n'
+ else
+ log_warning 'Non-interactive tty detected. Assuming empty password.'
+ db_password=''
+ fi
+ fi
+ ;;
+ # Specify database name
+ -n|--name)
+ shift
+ db_name=${1-}
+ if [ -z "${db_name}" ]; then
+ log_error "-n or --name requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ -d|--directory)
+ shift
+ scripts_dir=${1-}
+ if [ -z "${scripts_dir}" ]; then
+ log_error "-d or --directory requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # specify DHCPv4 lease type
+ -4)
+ if [ ${dhcp_version} -eq 6 ]; then
+ log_error "you may not specify both -4 and -6"
+ usage
+ exit 1
+ fi
+ dhcp_version=4
+ ;;
+ # specify DHCPv6 lease type
+ -6)
+ if [ ${dhcp_version} -eq 4 ]; then
+ log_error "you may not specify both -4 and -6"
+ usage
+ exit 1
+ fi
+ dhcp_version=6
+ ;;
+ # specify input file, used by lease-upload
+ -i|--input)
+ shift
+ input_file=${1-}
+ if [ -z "${input_file}" ]; then
+ log_error '-i or --input requires a parameter'
+ usage
+ exit 1
+ fi
+ ;;
+ # specify output file, currently only used by lease dump
+ -o|--output)
+ shift
+ dump_file=${1-}
+ if [ -z "${dump_file}" ]; then
+ log_error "-o or --output requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # specify extra arguments to pass to the database command
+ -x|--extra)
+ shift
+ if [ -z "${1-}" ]; then
+ log_error "-x or --extra requires a parameter"
+ usage
+ exit 1
+ fi
+ if [ -z "${extra_arguments}" ]; then
+ extra_arguments=${1}
+ else
+ extra_arguments="${extra_arguments} ${1}"
+ fi
+ ;;
+ -y|--yes)
+ assume_yes=1
+ ;;
+ *)
+ log_error "invalid option: ${option}"
+ usage
+ exit 1
+ esac
+ shift
+done
+
+# After all the parameters have been parsed, check environment variables.
+if test -z "${password_parameter_passed+x}"; then
+ if test -n "${KEA_ADMIN_DB_PASSWORD+x}"; then
+ printf 'Using the value of KEA_ADMIN_DB_PASSWORD for authentication...\n'
+ db_password="${KEA_ADMIN_DB_PASSWORD}"
+ fi
+fi
+
+case ${command} in
+ # Initialize the database
+ db-init)
+ case ${backend} in
+ memfile)
+ memfile_init
+ ;;
+ mysql)
+ mysql_init
+ ;;
+ pgsql)
+ pgsql_init
+ ;;
+ esac
+ ;;
+ db-version)
+ case ${backend} in
+ memfile)
+ memfile_version
+ ;;
+ mysql)
+ checked_mysql_version
+ printf '\n'
+ ;;
+ pgsql)
+ checked_pgsql_version
+ ;;
+ esac
+ ;;
+ db-upgrade)
+ case ${backend} in
+ memfile)
+ memfile_upgrade
+ ;;
+ mysql)
+ mysql_upgrade
+ ;;
+ pgsql)
+ pgsql_upgrade
+ ;;
+ esac
+ ;;
+ lease-dump)
+ case ${backend} in
+ memfile)
+ memfile_dump
+ ;;
+ mysql)
+ mysql_dump
+ ;;
+ pgsql)
+ pgsql_dump
+ ;;
+ esac
+ ;;
+ lease-upload)
+ case ${backend} in
+ memfile)
+ log_error 'lease-upload is not supported for memfile'
+ exit 1
+ ;;
+ mysql)
+ lease_upload
+ ;;
+ pgsql)
+ lease_upload
+ ;;
+ esac
+ ;;
+ stats-recount)
+ case ${backend} in
+ memfile)
+ log_info "memfile does not keep lease statistics"
+ ;;
+ mysql)
+ mysql_recount
+ ;;
+ pgsql)
+ pgsql_recount
+ ;;
+ esac
+ ;;
+esac
+
+exit 0