#!/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

function cli_config() {
	if cli_help_requested $@; then
		cli_show_man network-config
		exit ${EXIT_OK}
	fi

	if [ -n "${1}" ]; then
		config_set $@
		network_config_write
	else
		network_config_print
	fi
}

function cli_device() {
	local device=${1}
	local action=${2}
	shift 2

	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} $@
			;;
		status)
			cli_device_status ${device}
			;;
		*)
			cli_show_man network-device
			;;
	esac

	return ${EXIT_OK}
}

function cli_device_status() {
	local device=${1}
	assert device_exists ${device}

	# 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}"

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

	case "${status}" in
		${EXIT_TRUE})
			status="${COLOUR_GREEN}UP${COLOUR_NORMAL}"
			;;
		${EXIT_FALSE})
			status="${COLOUR_RED}DOWN${COLOUR_NORMAL}"
			;;
	esac

	cli_print_fmt1 1 "Status"	"${status}"
	cli_print_fmt1 1 "Type"		"${type}"
	cli_print_fmt1 1 "Address"	"$(device_get_address ${device})"
	cli_space

	# Print the link speed for ethernet devices.
	case "${type}" in
		ethernet)
			cli_print_fmt1 1 "Link" \
				"$(device_get_speed ${device}) MBit/s $(device_get_duplex ${device}) duplex"
			;;
	esac

	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 virtual devices.
	local virtuals=$(device_get_virtuals ${device})
	if [ -n "${virtuals}" ]; then
		cli_headline 2 "Virtual devices"

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

}

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

	local device_type=$(device_get_type ${device})
	if [ "${device_type}" != "real" ]; 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}
}

function 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}
}

function 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

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

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

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

function 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)
				action="up"
				;;
			stop)
				action="down"
				;;
			show)
				action="status"
				;;
		esac

		case "${action}" in
			config|down|edit|port|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
			create)
				zone_${action} $@
				;;
			remove)
				cli_zone_remove $@
				;;
			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
}

# 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.
function cli_zone_remove() {
	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_remove ${zone}
	else
		echo "Removing zone '${zone}' now..."
		zone_remove_now ${zone}
	fi

	exit ${EXIT_OK}
}

function 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
}

function 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
}

function 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
}

function 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 $@
}

function 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}
}

function 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_remove ${zone}
	done

	local port
	for port in $(ports_get --all); do
		port_remove ${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.
function 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
}

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

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

	case "${cmd}" in
		list)
			__dns_server_println "SERVER" "PRIORITY"
			dns_server_list
			exit ${EXIT_OK}
			;;
		add)
			log INFO "Adding new DNS server: ${server}..."
			dns_server_add $@
			;;
		remove)
			log INFO "Removing DNS server: ${server}..."
			dns_server_remove $@
			;;
		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_generate_resolvconf

	exit ${EXIT_OK}
}

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

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

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

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

exit ${EXIT_OK}
