#!/bin/awk -f

BEGIN {
	# split input fields by a colon
	FS=":"
	# set default language
	LANGUAGE="en_US.UTF8"
	# set translation domain
	TEXTDOMAIN = "alterator-firewall"
	readmsg=0
	LOGFILE="/var/log/configd.log"
	EFW="/etc/net/scripts/contrib/efw"
	IP="/sbin/ip"
	SERVPREFIX="/etc/alterator/alterator-firewall-"
	STDINFILE="/etc/net/ifaces/default/fw/iptables/filter/stdin"
	TABLEDIR="/etc/net/ifaces/default/fw/iptables"
}

# error reporting function
function debug(text, errno) {
	printf "%s " TEXTDOMAIN ": %s: %s\n", strftime("%B %d %H:%M:%S"), text, errno >> LOGFILE
	fflush(LOGFILE)
}

# efw parser
function efw(table, chain, command,		cmd, error, line, message, failure) {
	cmd = EFW " --iptables default " table " " chain " " command " 2>&1 > /dev/null"
	while ((error = cmd | getline line) > 0 && !failure)
		switch (line) {
			case /^iptables/:
				sub("^iptables[^:]*:", N_("Error:"), line)
				message = line
				break
			case /^ERROR/:
				failure++
				sub(EFW ":[[:space:]]+", "", line)
				sub("^ERROR:", N_("Command:"), line)
				message = message "\n" line
				break
		}
	if (error == -1)
		debug("Error efw call '" cmd "'", ERRNO)
	close(cmd)
	if (failure)
		return message
	else return ""
}

# add a chain to the table
function iptables_add_chain(table, chain,		file, error, line, cmd) {
	line = efw(table, chain, "new")
	if (line) {
		printf "(error \"%s\")", line
		return -1
	}
	file = TABLEDIR "/" table "/" chain
	if ((error = getline line <file) == -1)
		print "# created by " TEXTDOMAIN > file
	else {
		printf "(error \"%s\")", N_("Chain already exists")
		return -1
	}
	close(file)
	file = TABLEDIR "/" table "/loadorder"
	if ((error = getline line <file) == -1) {
		cmd = "ls -1 " TABLEDIR "/" table "/"
		while ((error = cmd | getline line) > 0) {
			if (line ~ /~$/ || line ~ /\.rpm/)
				continue
			print line >> file
		}
		if (error == -1)
			debug("Error reading chain list '" cmd "'", ERRNO)
		close(cmd)
	}
	else close(file)

	print chain >> file
	close(file)
	print "()"
}

# delete a chain from the table
function iptables_delete_chain(table, chain,		cmd, tempfile, file, error, line) {
	line = efw(table, chain, "unload")
	if (line) {
		printf "(error \"%s\")", line
		return -1
	}
	line = efw(table, chain, "delete")
	if (line) {
		printf "(error \"%s\")", line
		return -1
	}
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	file = TABLEDIR "/" table "/loadorder"
	while ((error = getline line <file) > 0)
		if (line != chain) {
			print line >> tempfile
			continue
		}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)

	close(file)
	close(tempfile)
	system("cat " tempfile " > " file)
	system("rm -f " tempfile)

	file = TABLEDIR "/" table "/" chain
	system("rm -f " file)
	print "()"
}

# choose if IP is internal ether external
function ip_location(addr,	ip) {
	if (match(addr, /^([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)$/, ip) == 0)
		return "wrong"
	else {
		# rfc1918 10/8 prefix
		if (ip[1] == 10)
			return N_("internal")
		# rfc1918 172.16/12 prefix
		else if (ip[1] == 172 && ip[2] >= 16 && ip[2] <= 31)
			return N_("internal")
		# rfc1918 192.168/16 prefix
		else if (ip[1] == 192 && ip[2] == 168)
			return N_("internal")
		# rfc3927 169.254/16 prefix
		else if (ip[1] == 169 && ip[2] == 254)
			return N_("internal")
		else
			return N_("external")
	}
}

# list interfaces
function iptables_list_ifaces(		cmd, error, line, list, iface, ip, loc, ifaces, idx, i, n) {
	cmd = IP " addr show"
	while ((error = cmd | getline line) > 0) {
		if (match(line, /^[[:digit:]]:+[[:space:]]+([^[:space:]]+):/, list) > 0) {
			iface = list[1]
			continue
		}
		if (match(line, /^[[:space:]]*inet[[:space:]]+([[:digit:].]+)\//, ip) > 0) {
			loc = ip_location(ip[1])
			if (loc != "wrong")
				ifaces[iface "/" ip[1]] = iface " - " ip[1] " - " loc
		}
	}
	n = asorti(ifaces, idx)
	for (i = 1; i <= n; i++) {
		if (idx[i] ~ /^lo\//)
			continue
		print "(\"" idx[i] "\" label \"" ifaces[idx[i]] "\")"
	}
	if (error == -1)
		debug("error reading interface list '" cmd "'", errno)
	close(cmd)
}

# check if port is accessible
function iptables_check_port(iface, ip, protocol, port,		file, error, line) {
	file = STDINFILE
	while ((error = getline line < file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (iface && line ~ /-i\y/)
			if (line !~ "-i[[:space:]]+" iface "(\\y|$)")
				continue
		if (ip && line ~ /-d\y/)
			if (line !~ "-d[[:space:]]+" ip "(\\y|$)")
				continue
		if (protocol && line ~ /-p\y/)
			if (line !~ "-p[[:space:]]+" protocol "(\\y|$)")
				continue
		if (port && line ~ /--dport\y/)
			if (line !~ "--dport[[:space:]]+" port "(\\y|$)")
				continue
		if (line !~ /-j[[:space:]]+ACCEPT(\y|$)/)
			error = -2
		break
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	# error = 0 -> EOF
	# error = 1 -> ACCEPT match found
	error--
	close(file)
	return error
}


# list services for interface
function iptables_list_services(iface, ip,	cmd, error1, error2, file, service, desc, domain, drop, line, list, n, i) {
	cmd = "ls -1 " SERVPREFIX "*"
	while ((error1 = cmd | getline file) > 0) {
		if (file ~ /~$/ || file ~ /\.rpm/)
			continue
		desc = ""
		drop = 0
		while ((error2 = getline line < file) > 0) {
			if (line ~ /^port/) {
				split(line, list, /[[:space:]]+/)
				drop = drop + iptables_check_port(iface, ip, list[2], list[3])
				continue
			}
			if (line !~ /^description\y/)
				continue
			if ((n = split(line, list, /[[:space:]]+/)) < 3)
				continue
			domain = list[2]
			desc = list[3]
			for(i = 4; i <= n; i++)
				desc = desc " " list[i]
		}
		if (error2 == -1)
			debug("Error reading file '" file "'", ERRNO)
		close(file)
		service = gensub(SERVPREFIX, "", "g", file)
		printf "(\"%s\" service \"%s\" description \"%s\" pass %s)",
		       service, service, N_(desc, domain), (drop != 0) ? "#f" : "#t"
	}
	if (error1 == -1)
		debug("Error running command '" cmd "'", ERRNO)
	close(cmd)
}

# write service for interface
function iptables_write_service(iface, ip, service, pass,	file, error, line, list, count, protocols, ports, cmd, tempfile, i) {
	file = SERVPREFIX service
	while ((error = getline line < file) > 0) {
		if (line !~ /^port/)
			continue
		split(line, list, /[[:space:]]+/)
		count++
		protocols[count] = list[2]
		ports[count] = list[3]
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)

	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	file = STDINFILE
	while ((error = getline line < file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/) {
			print line >> tempfile
			continue
		}
		if (iface && line ~ /-i\y/)
			if (line !~ "-i[[:space:]]+" iface "(\\y|$)") {
				print line >> tempfile
				continue
			}
		if (ip && line ~ /-d\y/)
			if (line !~ "-d[[:space:]]+" ip "(\\y|$)") {
				print line >> tempfile
				continue
			}
		if (line ~ /-p\y/) {
			for (i = 1; i <= count; i++)
				if (protocols[i]) {
				  if (line ~ "-p[[:space:]]+" protocols[i] "(\\y|$)")
				    if (ports[i] && line ~ /--dport\y/) {
				      if (line ~ "--dport[[:space:]]+" ports[i] "(\\y|$)")
					break
				    }
				    else break
				}
				else break
			if (i > count) {
				print line >> tempfile
				continue
			}
		}
		if (line ~ /--dport\y/) {
			for (i = 1; i <= count; i++)
				if (ports[i] && line ~ "--dport[[:space:]]+" ports[i] "(\\y|$)")
					if (protocols[i] && line ~ /-p\y/)
						if (line ~ "-p[[:space:]]+" protocols[i] "(\\y|$)")
							break
			if (i > count) {
				print line >> tempfile
				continue
			}
		}
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)

	for (i = 1; i <= count; i++)
		printf("%s %s %s %s %s\n", (iface) ? "-i " iface : "", (ip) ? "-d " ip : "",
			(protocols[i]) ? "-p " protocols[i] : "",
			(ports[i]) ? "--dport " ports[i] : "",
			(pass) ? "-j ACCEPT" : "-j REJECT") >> tempfile
	close(tempfile)
	system("cat " tempfile " > " file)
	system("rm -f " tempfile)
}

# set default service rules in stdin chain
function iptables_reset_services(	cmd, error1, error2, line, iface, list, ip, loc, count, ifaces, ips, locs, file, i, service) {
	print "" > STDINFILE
	cmd = IP " addr show"
	while ((error1 = cmd | getline line) > 0) {
		if (match(line, /^[[:digit:]]:+[[:space:]]+([^[:space:]]+):/, list) > 0) {
			iface = list[1]
			continue
		}
		if (match(line, /^[[:space:]]*inet[[:space:]]+([[:digit:].]+)\//, ip) > 0) {
			loc = ip_location(ip[1])
			if (loc != "wrong" && iface != "lo") {
				count++
				ifaces[count] = iface
				ips[count] = ip[1]
				locs[count] = loc
			}
		}
	}
	if (error1 == -1)
		debug("Error running command '" cmd "'", ERRNO)
	close(cmd)

	cmd = "ls -1 " SERVPREFIX "*"
	while ((error1 = cmd | getline file) > 0) {
		if (file ~ /~$/ || file ~ /\.rpm/)
			continue
		while ((error2 = getline line < file) > 0) {
			if (line !~ /^port/)
				continue
			split(line, list, /[[:space:]]+/)
			service = gensub(SERVPREFIX, "", "g", file)
			for (i = 1; i <= count; i++)
				printf("%s %s %s %s %s\n",
					ifaces[i] ? "-i " ifaces[i] : "",
					(ips[i]) ? "-d " ips[i] : "",
					(list[2]) ? "-p " list[2] : "",
					(list[3]) ? "--dport " list[3] : "",
					(locs[i] == N_("internal") ||
					 service == "conf" ||
					 service == "smtp" ||
					 service == "icmp") ? "-j ACCEPT" : "-j REJECT") >> STDINFILE
		}
		if (error2 == -1)
			debug("Error reading file '" file "'", ERRNO)
		close(file)
	}
	if (error1 == -1)
		debug("Error running command '" cmd "'", ERRNO)
	close(cmd)
	close(STDINFILE)
	line = efw("filter", "stdin", "reload")
	if (line) {
		printf "(error \"%s\")", line
		return -1
	}
}

# set default chain jump
function iptables_write_jump(table, chain, target,		cmd, tempfile, done, file, error, line, tmp) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	done = 0
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line < file) > 0) {
		if (line ~ "-j " target "(\\y|$)")
				continue
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/ || done) {
				print line >> tempfile
				continue
		}
		print "-j " target >> tempfile
		print line >> tempfile
		done = 1
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)

	if (!done)
		print "-j " target >> tempfile
	close(tempfile)

	cmd = "mktemp"
	cmd | getline tmp
	close(cmd)

	system("cat " file " > " tmp)
	system("cat " tempfile " > " file)
	# test new rules
	line = efw(table, chain, "reload")
	if (line) {
#		printf "(error \"%s\")", line
		# rollback
		system("cat " tmp " > " file)
		efw(table, chain, "reload")
	}
#	else
#		print "()"
	system("rm -f " tempfile)
	system("rm -f " tmp)
}

# show translation
# overwriting locale settings
function N_(text, domain,			list, cmd, line) {
	if (!domain)
		domain = TEXTDOMAIN
	split(LANGUAGE, list, /:/)
	cmd = "LANGUAGE=\"" LANGUAGE "\" LANG=\"" list[1] ".UTF8\" gettext " domain " \"" text "\""
	cmd | getline line
	close(cmd)
	return line
}

# start message reading
/^_message:begin$/ {
	readmsg=1
	next
}

# stop message reading
/^_message:end$/ {
	readmsg=0
	#debug("==========", "==========")
	#for (attribute in params)
	#	debug("params[" attribute "]", params[attribute])
	# overwrite current language
	if (params["language"] != "")
		LANGUAGE = gensub(/;/, ":", "g", params["language"])

	switch (params["action"]) {
		case "constraints":
			print "("
			printf " name (label \"%s\")", N_("Interface")
			printf " service (label \"%s\")", N_("Service")
			printf " pass (default #f label \"%s\")", N_("Pass")
		#	print  " share (default #f)"
			if (params["reset"] != "") {
				iptables_delete_chain("filter", "stdin")
				iptables_delete_chain("filter", "stdfwd")
				iptables_delete_chain("filter", "stdout")
				iptables_add_chain("filter", "stdin")
				iptables_add_chain("filter", "stdfwd")
				iptables_add_chain("filter", "stdout")
				iptables_reset_services()
				iptables_write_jump("filter", "INPUT", "stdin")
				iptables_write_jump("filter", "FORWARD", "stdfwd")
				iptables_write_jump("filter", "OUTPUT", "stdout")
			}
			print ")"
			break
		case "list":
			print "("
			if (match(params["_objects"], /^([^/]+)\/([^/]+)\/services$/, a) > 0)
					iptables_list_services(a[1], a[2])
			else
					iptables_list_ifaces()
			print ")"
			break
		case "write":
			if (params["service"] != "" && params["reset"] == "")
				if (match(params["_objects"], /^([^/]+)\/([^/]+)$/, a) > 0) {
					iptables_write_service(a[1], a[2],
						params["service"], params["pass"] == "#t")
					line = efw("filter", "stdin", "reload")
					if (line)
						printf "(error \"%s\")", line
					else print "()"
					break
				}
			print "()"

			break
		case "read":
		case "new":
		case "delete":
			print "()"
			break
		default:
			print "#f"
	}
	fflush()
	# delete attribute/value pairs before next cycle
	delete params
	#exit
	next
}

# save attribute/value pairs
{
	if (! readmsg)
		next
	attribute=$1
	value=$2
	# join the rest of fields with a colon
	for (n = 3; n <= NF; n++)
		value=value ":" $n
	value = gensub(/([^\\])\\n/, "\\1\n", "g", value)
	params[attribute]=value
}
