# Mgmt
# Copyright (C) James Shubin and the project contributors
# Written by James Shubin <james@shubin.ca> and the project contributors
#
# 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 <https://www.gnu.org/licenses/>.
#
# Additional permission under GNU GPL version 3 section 7
#
# If you modify this program, or any covered work, by linking or combining it
# with embedded mcl code and modules (and that the embedded mcl code and
# modules which link with this program, contain a copy of their source code in
# the authoritative form) containing parts covered by the terms of any other
# license, the licensors of this program grant you additional permission to
# convey the resulting work. Furthermore, the licensors of this program grant
# the original author, James Shubin, additional permission to update this
# additional permission if he deems it necessary to achieve the goals of this
# additional permission.

import "net"

import "git://github.com/purpleidea/mgmt/modules/dhcp/"
import "git://github.com/purpleidea/mgmt/modules/misc/"
import "git://github.com/purpleidea/mgmt/modules/purpleidea/"
import "git://github.com/purpleidea/mgmt/modules/shorewall/"

class router($st) {
	$net_dev = $st->net_dev || "eth0"
	$net_mac = $st->net_mac

	#$net_uuid = $st->net_uuid || "" # 01234567-89ab-cdef-0123-456789abcdef

	$loc_dev = $st->loc_dev || ""
	$loc_mac = $st->loc_mac || ""

	#$loc_uuid = $st->loc_uuid || "" # 01234567-89ab-cdef-0123-456789abcdef
	$loc_network = $st->loc_network # cidr

	$loc_ip = $st->loc_ip || net.cidr_to_first($loc_network) # no cidr
	$loc_broadcast = $st->loc_broadcast || net.cidr_to_last($loc_network)

	$loc_range = $st->loc_range || struct{start => "", end => "",} # struct with start, end fields
	$dns = $st->dns

	svc "NetworkManager" {
		state => "stopped",
		startup => "disabled",
	}

	include misc.network_rename($net_mac, $net_dev)
	if $loc_dev != "" and $loc_mac != "" {
		include misc.network_rename($loc_mac, $loc_dev)
	}

	if $net_dev != "" {
		# XXX: should we specify DNS here?
		include misc.networkd_dhcp($net_dev, struct{
			dns => $dns,
		})
		#include misc.network_manager_dhcp(struct{
		#	uuid => $net_uuid,
		#	dev => $net_dev,
		#	mac => $net_mac,
		#	dns => $dns,
		#})
	}

	if $loc_dev != "" {
		$prefix = net.cidr_to_prefix($loc_network)
		include misc.networkd_static($loc_dev, struct{
			cidr => "${loc_ip}/${prefix}",
			#dns => $dns,
		})
		#include misc.network_manager_static(struct{
		#	uuid => $loc_uuid,
		#	dev => $loc_dev, # formerly enp0s2
		#	mac => $loc_mac,
		#	cidr => "${loc_ip}/${prefix}",
		#	dns => $dns,
		#})
	}

	include purpleidea.base() # adds some utility packages

	include dhcp.server(struct{
		authoritative => true,
	}) as dhcp_server # good!

	include dhcp_server.subnet("loc", struct{
		network => net.cidr_to_ip($loc_network), # network address
		netmask => net.cidr_to_mask($loc_network), # eg: 255.255.255.0
		router => $loc_ip, # first address
		broadcast => $loc_broadcast, # last address
		range => $loc_range,
		dns => $dns,
		comment => "internal network",
	})

	#include dhcp_server.host("computer1", struct{
	#	macaddress => "00:11:22:33:44:55",
	#	fixedaddress => ["192.168.100.13",],
	#	hostname => "computer1",
	#})

	include shorewall.prepare() # add sysctl and disable firewalld
	include shorewall.firewall() as firewall

	# TODO: can I pass a list in instead?
	include firewall.zone("net", struct{})
	include firewall.zone("loc", struct{})

	# eg: eth0
	include firewall.interface("${net_dev}", "net", struct{
		interface => "NET_IF",	# default if not specified
		options => [
			"dhcp",
			"tcpflags",
			"nosmurfs",
			"routefilter",
			"sourceroute=0",
			"logmartians",
		],
	})

	include firewall.interface("${loc_dev}", "loc", struct{
		interface => "LOC_IF",	# default if not specified
		options => [
			"dhcp",
			"tcpflags",
			"nosmurfs",
			"routefilter",
			"logmartians",
		],
	})

	include firewall.policy("000-fw-net-ACCEPT", struct{
		source => "fw",
		dest => "net",
		policy => "ACCEPT",
	})

	include firewall.policy("000-loc-net-ACCEPT", struct{
		source => "loc",
		dest => "net",
		policy => "ACCEPT",
	})

	include firewall.policy("888-net-all-DROP", struct{
		source => "net",
		dest => "all",
		policy => "DROP",
		log => true,
	})
	include firewall.policy("999-all-all-REJECT", struct{
		source => "all",
		dest => "all",
		policy => "REJECT",
		log => true,
		comment => "THE FOLLOWING POLICY MUST BE LAST",
	})

	include firewall.snat("masq", struct{
		action => "MASQUERADE",
		source => ["${loc_network}",], # ip/cidr of the LOC network, eg: 192.168.100.0/24
		dest => "NET_IF",
	})

	include firewall.stoppedrule("loc-all", struct{
		action => "ACCEPT",
		source => "LOC_IF",
		dest => "-", # all
	})
	include firewall.stoppedrule("all-loc", struct{
		action => "ACCEPT",
		source => "-", # all
		dest => "LOC_IF",
	})

	include firewall.rule("000-invalid-net-all-tcp", struct{
		action => "Invalid(DROP)",
		source => "net",
		dest => "all",
		proto => "tcp",
		comment => "Don't allow connection pickup from the net",
	})

	include firewall.rule("111-accept-ssh-loc-fw", struct{
		action => "SSH(ACCEPT)",
		source => "loc",
		dest => "$FW",
		#proto => "",
		#port => "",
		comment => "local administration",
	})

	include firewall.rule("111-accept-ssh-net-fw", struct{
		action => "SSH(ACCEPT)",
		source => "net",
		dest => "$FW",
		#proto => "",
		#port => "",
		comment => "remote administration",
	})

	include firewall.rule("997-drop-ping-net-fw", struct{
		action => "Ping(DROP)",
		source => "net",
		dest => "$FW",
		comment => "Drop Ping from the \"bad\" net zone and prevent your log from being flooded.",
	})

	include firewall.rule("998-accept-icmp-fw-loc", struct{
		action => "ACCEPT",
		source => "$FW",
		dest => "loc",
		proto => "icmp",
	})
	include firewall.rule("999-accept-icmp-fw-net", struct{
		action => "ACCEPT",
		source => "$FW",
		dest => "net",
		proto => "icmp",
	})
}

class router:dhcp_host($st) {
	$h = $st->hostname
	$m = $st->macaddress
	$i = $st->ipaddress

	# TODO: add these checks
	#fail(not net.in_network($loc_network, $i))
	#$range_start = $loc_range->start || ""
	#$range_end = $loc_range->end || ""
	#if $range_start != "" and $range_end != "" {
	#	fail(net.in_range($range_start, $range_end, $i))
	#}

	include dhcp_server.host("${h}", struct{
		macaddress => "${m}",
		fixedaddress => ["${i}",],
		hostname => "${h}",
	})
}
