#!/bin/bash
###############################################################################
#                                                                             #
# IPFire.org - A linux based firewall                                         #
# Copyright (C) 2010  Michael Tremer & Christian Schmidt                      #
#                                                                             #
# This program is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU General Public License as published by        #
# the Free Software Foundation, either version 3 of the License, or           #
# (at your option) any later version.                                         #
#                                                                             #
# 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 General Public License for more details.                                #
#                                                                             #
# You should have received a copy of the GNU General Public License           #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
#                                                                             #
###############################################################################

# Parse the command line
while [ $# -gt 0 ]; do
	case "${1}" in
		-d|--debug)
			DEBUG=1
			;;
		*)
			action=${1}
			;;
	esac
	shift
	[ -n "${action}" ] && break
done

. /usr/lib/network/functions

# Read network settings
network_settings_read

cli_settings() {
	if cli_help_requested $@; then
		cli_show_man network-settings
		exit ${EXIT_OK}
	fi

	if [ -n "${1}" ]; then
		settings_set $@
		network_settings_write
	else
		network_settings_print
	fi
}

cli_device() {
	if cli_help_requested $@; then
		cli_show_man network-device
		exit ${EXIT_OK}
	fi

	local action="${1}"
	shift

	case "${action}" in
		list)
			cli_device_list $@
			;;
		*)
			local device="${action}"
			action="${1}"
			shift

			if ! isset device; then
				cli_show_man network-device
				return ${EXIT_ERROR}
			fi

			assert device_exists ${device}

			case "${action}" in
				discover)
					cli_device_discover ${device} $@
					;;
				identify)
					device_identify "${device}" $@
					;;
				monitor)
					cli_device_monitor "${device}" $@
					;;
				status)
					cli_device_status ${device}
					;;
				unlock)
					cli_device_serial_unlock ${device} $@
					;;
				ussd)
					cli_device_send_ussd_command "${device}" $@
					;;
				*)
					cli_show_man network-device
					;;
			esac
			;;
	esac

	return ${EXIT_OK}
}

cli_device_status() {
	local device="${1}"
	assert isset device

	# Disable debugging output here.
	local log_disable_stdout=${LOG_DISABLE_STDOUT}
	LOG_DISABLE_STDOUT="true"

	# Save the type of the device for later.
	local type=$(device_get_type ${device})

	cli_headline 1 "Device status: ${device}"
	cli_print_fmt1 1 "Name"		"${device}"

	# Handle special devices
	case "${type}" in
		phy)
			cli_device_status_phy "${device}"
			return $?
			;;
		serial)
			cli_device_status_serial "${device}"
			return $?
			;;
	esac

	# Print the device status.
	device_is_up ${device} &>/dev/null
	local status=$?

	case "${status}" in
		${EXIT_TRUE})
			status="${CLR_GREEN_B}UP${CLR_RESET}"
			;;
		${EXIT_FALSE})
			status="${CLR_RED_B}DOWN${CLR_RESET}"
			;;
	esac

	cli_print_fmt1 1 "Status"	"${status}"
	cli_print_fmt1 1 "Type"		"${type}"

	# Ethernet-compatible?
	device_is_ethernet_compatible "${device}" &>/dev/null
	cli_print_fmt1 1 "Ethernet-compatible" "$(cli_print_bool $?)"

	cli_print_fmt1 1 "Address"	"$(device_get_address ${device})"
	cli_space

	# Print the link speed for ethernet devices.
	if device_is_up ${device} &>/dev/null; then
		local link="$(device_get_link_string "${device}")"
		if isset link; then
			cli_print_fmt1 1 "Link" "${link}"
		fi
	fi

	cli_print_fmt1 1 "MTU"		"$(device_get_mtu ${device})"
	cli_space

	# Print device statistics.
	cli_device_stats 2 ${device}

	# Print some more information.
	device_has_carrier ${device} &>/dev/null
	cli_print_fmt1 1 "Has carrier?"	"$(cli_print_bool $?)"

	device_is_promisc ${device} &>/dev/null
	cli_print_fmt1 1 "Promisc"	"$(cli_print_bool $?)"
	cli_space

	# Print all vlan devices.
	local vlans=$(device_get_vlans ${device})
	if [ -n "${vlans}" ]; then
		cli_headline 2 "VLAN devices"

		local vlan
		for vlan in ${vlans}; do
			cli_print 2 "* %-6s - %s" "${vlan}" "$(device_get_address ${vlan})"
		done
		cli_space
	fi

	case "${type}" in
		wireless*)
			local phy="$(device_get_phy "${device}")"
			if isset phy; then
				cli_headline 2 "PHY"
				cli_print_fmt1 2 "Name" "${phy}"
				cli_print_fmt1 2 "Address" "$(phy_get_address "${phy}")"
				cli_space
			fi
			;;
	esac

	# Reset the logging level.
	LOG_DISABLE_STDOUT=${log_disable_stdout}
}

cli_device_status_serial() {
	local device=${1}
	assert device_is_serial ${device}

	serial_is_locked ${device} &>/dev/null
	local locked=$?

	cli_print_fmt1 1 "Locked" "$(cli_print_bool ${locked})"
	cli_space

	# Cannot go on when the device is locked.
	[ ${locked} -eq ${EXIT_TRUE} ] && return ${EXIT_OK}

	cli_print_fmt1 1 "Manufacturer" \
		"$(modem_get_manufacturer ${device})"
	cli_print_fmt1 1 "Model" \
		"$(modem_get_model ${device})"
	cli_print_fmt1 1 "Software version" \
		"$(modem_get_software_version ${device})"

	# Mobile
	if modem_is_mobile "${device}"; then
		cli_print_fmt1 1 "IMEI" \
			"$(modem_get_device_imei ${device})"
	fi
	cli_space

	if modem_is_mobile "${device}"; then
		modem_mobile_network_status "${device}" 2
		cli_space
	fi
}

cli_device_status_phy() {
	local phy="${1}"
	assert phy_exists "${phy}"

	local address="$(phy_get_address "${phy}")"
	cli_print_fmt1 1 "Address" "${address}"
	cli_space

	local devices="$(phy_get_devices "${phy}")"
	if isset devices; then
		cli_headline 2 "Soft interfaces"

		local device
		for device in ${devices}; do
			cli_print 2 "* %s" "${device}"
		done
		cli_space
	fi

	return ${EXIT_OK}
}

cli_device_discover() {
	local device=${1}
	shift

	# This can only be executed for ethernet (or compatible) devices
	if ! device_is_ethernet_compatible "${device}"; then
		return ${EXIT_OK}
	fi

	local raw

	while [ $# -gt 0 ]; do
		case "${1}" in
			--raw)
				raw=1
				;;
		esac
		shift
	done

	local up
	device_is_up ${device} && up=1
	device_set_up ${device}

	enabled raw || echo "${device}"

	local hook
	local out
	local ret
	for hook in $(hook_zone_get_all); do
		out=$(hook_zone_exec ${hook} discover ${device})
		ret=$?

		[ ${ret} -eq ${DISCOVER_NOT_SUPPORTED} ] && continue

		if enabled raw; then
			case "${ret}" in
				${DISCOVER_OK})
					echo "${hook}: OK"
					local line
					while read line; do
						echo "${hook}: ${line}"
					done <<<"${out}"
					;;

				${DISCOVER_ERROR})
					echo "${hook}: FAILED"
					;;
			esac
		else
			case "${ret}" in
				${DISCOVER_OK})
					echo "  ${hook} was successful."
					local line
					while read line; do
						echo "  ${line}"
					done <<<"${out}"
					;;

				${DISCOVER_ERROR})
					echo "  ${hook} failed."
					;;
			esac
		fi
	done

	echo # New line

	[ "${up}" = "1" ] || device_set_down ${device}
}

cli_device_serial_unlock() {
	if cli_help_requested $@; then
		cli_show_man network-device
		exit ${EXIT_OK}
	fi

	local device=${1}
	assert isset device

	if ! device_is_serial ${device}; then
		error "${device} is not a serial device."
		error "Unlocking is only supported for serial devices."
		exit ${EXIT_ERROR}
	fi

	# Read the current state of the SIM card.
	modem_sim_status ${device} &>/dev/null
	local sim_status_code=$?

	# If the SIM card is already unlocked, we don't need to do anything.
	if [ ${sim_status_code} -eq ${EXIT_SIM_READY} ]; then
		print "The SIM card is already unlocked."
		exit ${EXIT_OK}

	# If the SIM card is in an unknown state, we cannot do anything.
	elif [ ${sim_status_code} -eq ${EXIT_SIM_UNKNOWN} ]; then
		error "The SIM card is in an unknown state."
		exit ${EXIT_ERROR}
	fi

	# Ask for the code.
	local code=${2}
	local require_new_pin="false"
	local new_pin

	while ! isinteger code; do
		local message
		case "${sim_status_code}" in
			${EXIT_SIM_PIN})
				message="Please enter PIN:"
				;;
			${EXIT_SIM_PUK})
				message="Please enter PUK:"
				require_new_pin="true"
				;;
		esac
		assert isset message

		echo -n "${message} "
		read -s code
		echo # Print newline.

		if enabled require_new_pin; then
			local i new_pin2
			for i in 0 1; do
				case "${i}" in
					0)
						message="Please enter a new PIN code:"
						;;
					1)
						message="Please confirm the new PIN code:"
						;;
				esac

				echo -n "${message} "
				read -s new_pin2
				echo # Print newline.

				if [ -n "${new_pin}" ]; then
					if [ "${new_pin}" != "${new_pin2}" ]; then
						error "The entered PIN codes did not match."
						exit ${EXIT_ERROR}
					fi
				else
					new_pin=${new_pin2}
				fi
			done
		fi
	done

	# Trying to unlock the SIM card.
	modem_sim_unlock ${device} ${code} ${new_pin}

	exit $?
}

cli_device_send_ussd_command() {
	local device="${1}"
	assert isset device
	shift

	local command
	local timeout

	while [ $# -gt 0 ]; do
		case "${1}" in
			--timeout=*)
				timeout="$(cli_get_val "${1}")"
				;;
			*)
				if isset command; then
					warning "Unrecognized argument: ${1}"
				else
					command="${1}"
				fi
				;;
		esac
		shift
	done

	assert device_is_serial "${device}"

	local args
	if isset timeout; then
		args="${args} --timeout=${timeout}"
	fi

	modem_ussd_send_command "${device}" "${command}" ${args}
	exit $?
}

cli_device_monitor() {
	local device="${1}"
	assert isset device

	if ! device_is_wireless "${device}"; then
		error "This action only works with wireless devices. Exiting."
		exit ${EXIT_ERROR}
	fi

	wireless_monitor "${device}"
	exit $?
}

cli_device_list() {
	local device
	for device in $(device_list); do
		cli_device_status "${device}"
	done

	exit ${EXIT_OK}
}

cli_hostname() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	local hostname=${1}

	if [ -n "${hostname}" ]; then
		config_hostname ${hostname}
		log INFO "Hostname was set to '${hostname}'."
		log INFO "Changes do only take affect after reboot."
		exit ${EXIT_OK}
	fi

	echo "$(config_hostname)"
	exit ${EXIT_OK}
}

cli_port() {
	if cli_help_requested $@; then
		cli_show_man network-port
		exit ${EXIT_OK}
	fi

	local action
	local port

	if port_exists ${1}; then
		port=${1}
		action=${2}
		shift 2

		case "${action}" in
			edit|create|remove|up|down|status|identify)
				port_${action} "${port}" $@
				;;
			*)
				error "Unrecognized argument: ${action}"
				exit ${EXIT_ERROR}
				;;
		esac
	else
		action=${1}
		shift

		case "${action}" in
			new|destroy)
				port_${action} $@
				;;
			*)
				error "Unrecognized argument: ${action}"
				exit ${EXIT_ERROR}
				;;
		esac
	fi
}

cli_zone() {
	if cli_help_requested $@; then
		cli_show_man network-zone
		exit ${EXIT_OK}
	fi

	local action
	local zone

	if zone_name_is_valid ${1}; then
		zone=${1}
		action=${2}
		shift 2

		# Action aliases
		case "${action}" in
			start|reload)
				action="up"
				;;
			stop)
				action="down"
				;;
			show)
				action="status"
				;;
		esac

		case "${action}" in
			port)
				cli_zone_port "${zone}" $@
				;;
			rename)
				cli_zone_rename "${zone}" $@
				;;
			config|disable|down|edit|enable|identify|status|up)
				zone_${action} ${zone} $@
				;;
			*)
				error "Unrecognized argument: ${action}"
				cli_show_man network-zone
				exit ${EXIT_ERROR}
				;;
		esac
	else
		action=${1}
		shift

		case "${action}" in
			new)
				cli_zone_new $@
				;;
			destroy)
				cli_zone_destroy $@
				;;
			list-hooks)
				cli_list_hooks zone $@
				;;
			""|*)
				if [ -n "${action}" ]; then
					error "Unrecognized argument: '${action}'"
					echo
				fi

				cli_show_man network-zone
				exit ${EXIT_ERROR}
				;;
		esac
	fi
}

cli_zone_new() {
	if cli_help_requested $@ || [ $# -lt 2 ]; then
		cli_show_man network-zone-new
		exit ${EXIT_OK}
	fi

	zone_new $@
}

# Removes a zone either immediately, if it is currently down,
# or adds a tag that the removal will be done when the zone
# is brought down the next time.
cli_zone_destroy() {
	if cli_help_requested $@; then
		cli_show_man network-zone
		exit ${EXIT_OK}
	fi

	local zone="${1}"
	assert zone_exists "${zone}"

	if zone_is_up ${zone}; then
		echo "Zone '${zone}' is up and will be removed when it goes down the next time."
		zone_destroy "${zone}"
	else
		echo "Removing zone '${zone}' now..."
		zone_destroy_now "${zone}"
	fi

	exit ${EXIT_OK}
}

cli_zone_port() {
	if cli_help_requested $@; then
		cli_show_man network-zone-port
		exit ${EXIT_OK}
	fi

	local zone="${1}"
	assert zone_exists "${zone}"

	if port_exists "${2}"; then
		local port="${2}"
		local action="${3}"
		shift 3

		case "${action}" in
			edit)
				zone_port_edit "${zone}" "${port}" $@
				;;
			*)
				error "Unrecognised argument: ${action}"
				exit ${EXIT_ERROR}
				;;
		esac
	else
		local action="${2}"
		shift 2

		case "${action}" in
			attach)
				zone_port_attach "${zone}" $@
				;;
			detach)
				zone_port_detach "${zone}" $@
				;;
			*)
				error "Unrecognised argument: ${action}"
				exit ${EXIT_ERROR}
				;;
		esac
	fi

	exit ${EXIT_OK}
}

cli_zone_rename() {
	if cli_help_requested $@; then
		cli_show_man network-zone
		exit ${EXIT_OK}
	fi

	local zone="${1}"
	local name="${2}"
	shift 2

	if ! isset name; then
		error "You need to pass a new name"
		exit ${EXIT_ERROR}
	fi

	if ! zone_name_is_valid "${name}"; then
		error "Invalid new zone name: ${name}"
		exit ${EXIT_ERROR}
	fi

	# Check if the zone exists
	if ! zone_exists "${zone}"; then
		error "Zone ${zone} does not exist"
		exit ${EXIT_ERROR}
	fi

	# Destroyed zones cannot be renamed
	if zone_has_destroy_tag "${zone}"; then
		error "Zone ${zone} is about to be destroyed and cannot be renamed"
		exit ${EXIT_ERROR}
	fi

	# Check if a zone with the new name already exists
	if zone_exists "${name}"; then
		error "Zone ${name} already exists"
		exit ${EXIT_ERROR}
	fi

	# Rename
	if ! zone_rename "${zone}" "${name}"; then
		error "Could not rename zone ${zone} to ${name}"
		exit ${EXIT_ERROR}
	fi

	exit ${EXIT_OK}
}

cli_list_hooks() {
	local type=${1}
	shift

	if cli_help_requested $@; then
		cli_show_man network-zone
		exit ${EXIT_OK}
	fi

	local hook_dir=$(hook_dir ${type})
	local hook

	for hook in ${hook_dir}/*; do
		hook=$(basename ${hook})
		if hook_exists ${type} ${hook}; then
			echo "${hook}"
		fi
	done | sort -u
}

cli_route() {
	if cli_help_requested $@; then
		cli_show_man network-route
		exit ${EXIT_OK}
	fi

	local action=${1}
	shift

	case "${action}" in
		# Add a new route.
		add)
			route_add $@
			;;
		# Remove an existing route.
		remove)
			route_remove $@
			;;
		# List all routes.
		list)
			route_list $@
			return ${EXIT_OK}
			;;
		*)
			error "Unrecognized action: ${action}"
			cli_run_help network route

			exit ${EXIT_ERROR}
			;;
	esac

	# Applying all routes.
	route_apply

	exit ${EXIT_OK}
}

cli_dhcpd() {
	local proto=${1}
	shift

	if cli_help_requested $@; then
		cli_show_man network-dhcp
		exit ${EXIT_OK}
	fi

	local action=${1}
	shift

	case "${action}" in
		edit)
			dhcpd_edit ${proto} $@
			;;
		start)
			dhcpd_start ${proto}
			;;
		stop)
			dhcpd_stop ${proto}
			;;
		restart|reload)
			dhcpd_reload ${proto}
			;;
		subnet)
			cli_dhcpd_subnet ${proto} $@
			;;
		show|"")
			cli_dhcpd_show ${proto} $@
			;;
		*)
			error "Unrecognized action: ${action}"
			cli_run_help network dhcpvN

			exit ${EXIT_ERROR}
			;;
	esac

	exit ${EXIT_OK}
}

cli_dhcpd_show() {
	local proto=${1}
	assert isset proto

	local settings=$(dhcpd_settings ${proto})
	assert isset settings

	local ${settings}
	dhcpd_global_settings_read ${proto}

	cli_headline 1 "Dynamic Host Configuration Protocol Daemon for ${proto/ip/IP}"

	case "${proto}" in
		ipv6)
			cli_headline 2 "Lease times"
			if isinteger VALID_LIFETIME; then
				cli_print_fmt1 2 "Valid lifetime" "${VALID_LIFETIME}s"
			fi

			if isinteger PREFERRED_LIFETIME; then
				cli_print_fmt1 2 "Preferred lifetime" "${PREFERRED_LIFETIME}s"
			fi

			cli_space
			;;
		ipv4)
			cli_print_fmt1 1 "Authoritative" $(cli_print_enabled AUTHORITATIVE)
			cli_space

			cli_headline 2 "Lease times"
			cli_print_fmt1 2 "Default lease time" "${DEFAULT_LEASE_TIME}s"
			cli_print_fmt1 2 "Max. lease time" "${MAX_LEASE_TIME}s"

			if isset MIN_LEASE_TIME; then
				cli_print_fmt1 2 "Min. lease time" "${MIN_LEASE_TIME}s"
			fi

			cli_space
			;;
	esac

	# Read the options.
	local -A options
	dhcpd_global_options_read ${proto} ${subnet_id}

	# Print the options if any.
	if [ ${#options[*]} -gt 0 ]; then
		cli_headline 2 "Options"

		local option
		for option in $(dhcpd_options ${proto}); do
			[ -n "${options[${option}]}" ] || continue

			cli_print_fmt1 2 \
				"${option}" "${options[${option}]}"
		done
		cli_space
	fi

	# Subnets.
	local subnets=$(dhcpd_subnet_list ${proto})
	if [ -n "${subnets}" ]; then
		cli_headline 2 "Subnets"
		local subnet_id
		for subnet_id in ${subnets}; do
			cli_dhcpd_subnet_show ${proto} ${subnet_id} 2
		done
	fi
}

cli_dhcpd_subnet() {
	local proto=${1}
	shift

	if cli_help_requested $@; then
		cli_show_man network-dhcp-subnet
		exit ${EXIT_OK}
	fi

	local action=${1}
	shift

	case "${action}" in
		new)
			dhcpd_subnet_new ${proto} $@
			;;
		remove)
			dhcpd_subnet_remove ${proto} $@
			;;
		[0-9]*)
			local subnet_id=${action}

			if ! dhcpd_subnet_exists ${proto} ${subnet_id}; then
				error "The given subnet with ID ${subnet_id} does not exist."
				return ${EXIT_ERROR}
			fi

			# Update the action.
			action=${1}
			shift

			case "${action}" in
				edit)
					dhcpd_subnet_edit ${proto} ${subnet_id} $@
					local ret=$?

					if [ ${ret} -eq ${EXIT_OK} ]; then
						dhcpd_reload ${proto}
					fi
					exit ${ret}
					;;
				range)
					cli_dhcpd_subnet_range ${proto} ${subnet_id} $@
					exit $?
					;;
				show)
					cli_dhcpd_subnet_show ${proto} ${subnet_id} $@
					exit $?
					;;
				options)
					cli_dhcpd_subnet_options ${proto} ${subnet_id} $@
					exit $?
					;;
				*)
					error "Unrecognized action: ${action}"
					cli_run_help network dhcpvN subnet
					exit ${EXIT_ERROR}
					;;
			esac
			;;
		show)
			local subnet_id
			for subnet_id in $(dhcpd_subnet_list ${proto}); do
				cli_dhcpd_subnet_show ${proto} ${subnet_id}
			done
			;;
		*)
			error "Unrecognized action: ${action}"
			cli_run_help network dhcpvN subnet

			exit ${EXIT_ERROR}
			;;
	esac

	exit ${EXIT_OK}
}

cli_dhcpd_subnet_range() {
	local proto=${1}
	assert isset proto
	shift

	local subnet_id=${1}
	assert isset subnet_id
	shift

	local action=${1}
	shift

	case "${action}" in
		new)
			dhcpd_subnet_range_new ${proto} ${subnet_id} $@
			exit $?
			;;
		remove)
			dhcpd_subnet_range_remove ${proto} ${subnet_id} $@
			exit $?
			;;
		*)
			error "Unrecognized action: ${action}"
			cli_run_help network dhcpvN subnet range
			exit ${EXIT_ERROR}
			;;
	esac
}

cli_dhcpd_subnet_show() {
	local proto=${1}
	assert isset proto

	local subnet_id=${2}
	assert isset subnet_id

	local level=${3}
	isset level || level=0

	local $(dhcpd_subnet_settings ${proto})

	# Read in configuration settings.
	dhcpd_subnet_read ${proto} ${subnet_id}

	cli_headline $(( ${level} + 1 )) \
		"DHCP${proto/ip/} subnet declaration #${subnet_id}"
	cli_print_fmt1 $(( ${level} + 1 )) \
		"Subnet" "${ADDRESS}/${PREFIX}"
	cli_space

	# Read the options.
	local -A options
	dhcpd_subnet_options_read "${proto}" "${subnet_id}"

	# Print the options if any.
	if [ ${#options[*]} -gt 0 ]; then
		cli_headline $(( ${level} + 2 )) "Options"

		local option
		for option in $(dhcpd_subnet_options_list ${proto}); do
			[ -n "${options[${option}]}" ] || continue

			cli_print_fmt1 $(( ${level} + 2 )) \
				"${option}" "${options[${option}]}"
		done
		cli_space
	fi

	# Ranges.
	cli_headline $(( ${level} + 2 )) "Ranges"

	local ranges=$(dhcpd_subnet_range_list ${proto} ${subnet_id})
	if isset ranges; then
		local range_id $(dhcpd_subnet_range_settings ${proto})
		for range_id in ${ranges}; do
			dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}

			cli_print $(( ${level} + 2 )) \
				"#%d: %s - %s" ${range_id} ${START} ${END}
		done
	else
		cli_print $(( ${level} + 2 )) "No ranges have been defined."
	fi

	cli_space
}

cli_dhcpd_subnet_options() {
	local proto=${1}
	assert isset proto
	shift

	local subnet_id=${1}
	assert isset subnet_id
	shift

	local key val
	while [ $# -gt 0 ]; do
		case "${1}" in
			*=*)
				key=$(cli_get_key ${1})
				val=$(cli_get_val ${1})

				dhcpd_subnet_option_set ${proto} ${subnet_id} ${key} ${val}
		esac
	done
}

cli_start() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	local zones=$(zones_get $@)

	local zone
	for zone in ${zones}; do
		zone_start ${zone} &
	done

	wait # until everything is settled
}

cli_stop() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	local zones=$(zones_get $@)

	local zone
	for zone in ${zones}; do
		zone_stop ${zone} &
	done

	wait # until everything is settled
}

cli_restart() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	cli_stop $@

	# Give the system some time to calm down
	sleep ${TIMEOUT_RESTART}

	cli_start $@
}

cli_status() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	# When dumping status information, the debug
	# mode clutters the console which is not what we want.
	# Logging on the console is disabled for a short time.
	local log_disable_stdout=${LOG_DISABLE_STDOUT}
	LOG_DISABLE_STDOUT="true"

	local zones=$(zones_get $@)

	local zone
	for zone in ${zones}; do
		zone_status ${zone}
	done

	# Reset logging.
	LOG_DISABLE_STDOUT=${log_disable_stdout}
}

cli_reset() {
	if cli_help_requested $@; then
		cli_show_man network
		exit ${EXIT_OK}
	fi

	warning_log "Will reset the whole network configuration!!!"

	# Force mode is disabled by default
	local force=0

	while [ $# -gt 0 ]; do
		case "${1}" in
			--force|-f)
				force=1
				;;
		esac
		shift
	done

	# If we are not running in force mode, we ask the user if he does know
	# what he is doing.
	if ! enabled force; then
		if ! cli_yesno "Do you really want to reset the whole network configuration?"; then
			exit ${EXIT_ERROR}
		fi
	fi

	local zone
	for zone in $(zones_get --all); do
		zone_destroy_now "${zone}"
	done

	local port
	for port in $(ports_get --all); do
		port_destroy "${port}"
	done

	# Flush all DNS servers.
	dns_server_flush

	# Re-run the initialization functions
	init_run

	exit ${EXIT_OK}
}

# Help function: will show the default man page to the user.
# Optionally, there are two arguments taken, the type of hook
# and which hook should be shown.
cli_help() {
	local type=${1}
	local what=${2}

	# Remove unknown types.
	if ! listmatch ${type} zone port config; then
		type=""
	fi

	# If no arguments were given, we will show the default page.
	if [ -z "${type}" ]; then
		cli_show_man network
		return ${EXIT_OK}
	fi

	if ! hook_exists ${type} ${what}; then
		error "Hook of type '${type}' and name '${what}' could not be found."
		exit "${EXIT_ERROR}"
	fi

	hook_exec ${type} ${what} help
}

cli_dns_server() {
	if cli_help_requested $@; then
		cli_show_man network-dns-server
		exit ${EXIT_OK}
	fi

	# Get the command.
	local cmd=${1}; shift
	if [ -z "${cmd}" ]; then
		cli_show_man network-dns-server
		exit ${EXIT_ERROR}
	fi

	# Get the new server to process (if any).
	local server=${1}
	local priority=${2}

	case "${cmd}" in
		list)
			dns_server_show
			exit ${EXIT_OK}
			;;
		add)
			if dns_server_exists ${server}; then
				error "DNS server '${server}' already exists!"
				exit ${EXIT_ERROR}
			fi

			log INFO "Adding new DNS server: ${server}"
			dns_server_add ${server} ${priority}
			;;
		remove)
			if ! dns_server_exists ${server}; then
				error "DNS server '${server}' does not exist!"
				exit ${EXIT_ERROR}
			fi

			log INFO "Removing DNS server: ${server}"
			dns_server_remove ${server} ${priority}
			;;
		update)
			# Just run the update afterwards.
			;;
		*)
			error "No such command: ${cmd}"
			exit ${EXIT_ERROR}
	esac

	# Update the local DNS configuration after changes have been made.
	dns_server_update

	exit ${EXIT_OK}
}

cli_raw() {
	local cmd="${1}"
	assert isset cmd
	shift

	case "${cmd}" in
		db-dump)
			db_dump
			;;
		list-devices)
			device_list
			;;
		list-dhcpd-ranges-of-subnet)
			dhcpd_subnet_range_list $@
			;;
		list-dhcpd-settings)
			dhcpd_global_settings_list $@
			;;
		list-dhcpd-subnets)
			dhcpd_subnet_list $@
			;;
		list-dhcpd-subnet-options)
			dhcpd_subnet_options_list $@
			;;
		list-dns-servers)
			dns_server_list
			;;
		list-free-ports)
			port_list_free
			;;
		list-hooks)
			hook_list $@
			;;
		list-ports)
			port_list
			;;
		list-ports-of-zone)
			zone_get_ports $@
			;;
		list-settings)
			network_settings_list
			;;
		list-zones)
			zones_get_all
			;;
		*)
			error "No such command: ${cmd}"
			exit ${EXIT_ERROR}
			;;
	esac

	exit ${EXIT_OK}
}

# Process the given action
case "${action}" in
	init)
		init_run
		;;

	settings|hostname|port|device|zone|start|stop|restart|status|reset|route)
		cli_${action} $@
		;;

	# DHCP server configuration (automatically detects which protocol to use).
	dhcpv6|dhcpv4)
		cli_dhcpd ${action/dhcp/ip} $@
		;;

	# DNS server configuration.
	dns-server)
		cli_dns_server $@
		;;

	""|help|--help|-h)
		cli_help $@
		;;

	raw)
		cli_raw $@
		;;

	*)
		error "Invalid command given: ${action}"
		cli_usage "network help"
		exit ${EXIT_CONF_ERROR}
		;;
esac

exit ${EXIT_OK}
