#!/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"
	PROTOCOLS="/etc/protocols"
	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)
}

# djb2 hash function
function hash(text,		sum, i) {
	sum = 5381
	for(i = 1; i <= length(text); i++)
		sum = xor(lshift(sum, 5) + sum, substr(text, i, 1) + 0)
	return sprintf("%d", sum)
}

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

# list avalable interfaces and groups (eth+, lo+)
function iptables_list_ifaces(		cmd, error, line, list, iface, idx, i, n) {
	i = 1
	cmd = IP " addr show"
	while ((error = cmd | getline line) > 0)
		if (match(line, /^[[:digit:]]:+[[:space:]]+([^[:space:]]+):/, list) > 0) {
			n = list[1]
			iface[n] = i++
			if (match(n, /^(.+)[[:digit:]]+$/, list) > 0)
				if (iface[list[1] "+"] == "")
					iface[list[1] "+"] = i++
		}
	if (error == -1)
		debug("Error reading interface list '" cmd "'", ERRNO)
	close(cmd)
	n = asorti(iface, idx)
	for (i = 1; i <= n; i++)
		if (iface[idx[i]] > 0)
			print "(\"" idx[i] "\")"
}

# read protocol names from /etc/protocol
function iptables_list_protocols(	file, error, line, list) {
	file = PROTOCOLS
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (match(line, /^([^[:space:]]+)[[:space:]]+([[:digit:]]+)/, list) > 0)
			# tcp, udp and icmp are already known
			if (list[1] != "tcp" && list[1] != "udp" && list[1] != "icmp")
				printf ("(\"%d\" label \"%s\")", list[2], list[1])
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
}

# list rule numbers in a chain
function iptables_rule_numbers(table, chain,		file, error, line, num) {
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (line ~ /-P\y/)
			continue
		num++
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
	return num
}

# list chains in the table
function iptables_list_chains(table,		cmd, error, line, list, iface, ip, loc, ifaces, idx, i, n) {
	file = TABLEDIR "/" table "/loadorder"
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		print "(\"" line "\")"
	}
	if (error == -1) {
		debug("Error reading file '" file "'", ERRNO)
		cmd = "ls -1 " TABLEDIR "/" table "/"
		while ((error = cmd | getline line) > 0) {
			if (line ~ /~$/ || line ~ /\.rpm/)
				continue
			print "(\"" line "\")"
		}
		if (error == -1)
			debug("Error reading chain list '" cmd "'", ERRNO)
		close(cmd)
	}
	close(file)
}

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

# save a chain policy
function iptables_save_policy(table, chain, policy,		cmd, tempfile, file, done, error, line) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	done = 0
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /-P\y/)
			continue
		if (line ~ /^#/ || done) {
			print line >> tempfile
			continue
		}
		print "-P " policy >> tempfile
		print line >> tempfile
		done = 1
	}
	if (!done)
		print "-P " policy >> tempfile
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)

	close(file)
	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)
}

# read a chain policy
function iptables_read_policy(table, chain, 		file, error, line, list) {
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || done)
			continue
		if (match(line,/-P[[:space:]]+([^[:space:]]+)/, list) > 0) {
			close(file)
			return list[1]
		}
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)

	close(file)
	return ""
}

# list chain rules
function iptables_list_rules(table, chain,		num, file, error, line, hash, count, rule, list, addr, opt, state) {
	num = 1
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (line ~ /-P\y/)
			continue
		hash = hash(line)
		count[hash]++
		rule = iptables_parse_rule(line)
	
		printf "(\"%s\" number \"%d\"",  hash ";" count[hash], num

		if (match(rule, /fwaction[[:space:]]+"([^"]+)"/, list) > 0)
			printf " fwaction \"%s\"", list[1]
		if (match(rule, /protocol[[:space:]]+"([^"]+)"/, list) > 0)
			printf " protocol \"%s%s\"",
				(rule ~ /not_protocol[[:space:]]+#t/) ? "!" : "", list[1]
		
		addr = opt = ""
		if (match(rule, /source[[:space:]]+"([^"]+)"/, list) > 0) {
			addr = addr list[1]
			if (rule ~ /not_source[[:space:]]+#t/)
				addr = "!" addr
		}
		if (match(rule, /sport1[[:space:]]+"([^"]*)"/, list) > 0) {
			opt = list[1]
			if (match(rule, /sport2[[:space:]]+"([^"]+)"/, list) > 0)
				opt = opt ":" list[1]
			if (opt && rule ~ /not_sport[[:space:]]+#t/)
				opt = "!" opt
		}
		if (match(rule, /iface_in[[:space:]]+"([^"]+)"/, list) > 0) {
			opt = list[1] "," opt
			if (rule ~ /not_iface_in[[:space:]]+#t/)
				opt = "!" opt
		}
		sub(/,$/, "", opt)
		printf " source \"%s", addr
		if (opt)
			printf " (%s)", opt
		print "\""
		
		addr = opt = state = ""
		if (match(rule, /destination[[:space:]]+"([^"]+)"/, list) > 0) {
			addr = addr list[1]
			if (rule ~ /not_destination[[:space:]]+#t/)
				addr = "!" addr
		}
		if (match(rule, /dport1[[:space:]]+"([^"]*)"/, list) > 0) {
			opt = list[1]
			if (match(rule, /dport2[[:space:]]+"([^"]+)"/, list) > 0)
				opt = opt ":" list[1]
			if (opt && rule ~ /not_dport[[:space:]]+#t/)
				opt = "!" opt
		}
		if (match(rule, /iface_out[[:space:]]+"([^"]+)"/, list) > 0) {
			opt = list[1] "," opt
			if (rule ~ /not_iface_out[[:space:]]+#t/)
				opt = "!" opt
		}
		sub(/,$/, "", opt)
		if (rule ~ /check_invalid[[:space:]]+#t/)
			state = state ",I"
		if (rule ~ /check_new[[:space:]]+#t/)
			state = state ",N"
		if (rule ~ /check_established[[:space:]]+#t/)
			state = state ",E"
		if (rule ~ /check_related[[:space:]]+#t/)
			state = state ",R"
		sub(/^,/, "", state)
		printf " destination \"%s", addr
		if (opt)
			printf " (%s)", opt
		if (state)
			printf " [state:%s]", state
		print "\""
		
		print ")"
		num++
	}
	if (error == -1)
	    debug("Error reading file '" file "'", ERRNO)
	close(file)
}

# read the chain rule
function iptables_read_rule(table, chain, hash, num,		file, error, line, number, count) {
	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (line ~ /-P\y/)
			continue
		number++
		if (hash != hash(line))
			continue
		count++
		if (count != num)
			continue

		print "number \"" number "\"" iptables_parse_rule(line)
	}
	if (error == -1)
	    debug("Error reading file '" file "'", ERRNO)
	close(file)
}

# parse iptables rule
function iptables_parse_rule(line,	rule) {
		if (match(line, /-p[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_protocol %s protocol \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /-s[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_source %s source \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /-i[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_iface_in %s iface_in \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /-d[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_destination %s destination \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /-o[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_iface_out %s iface_out \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /(![[:space:]]+)?-f[[:space:]]+/, list) > 0)
			rule = rule sprintf(" not_fragment %s fragment #t", (list[1] ~ /^!/) ? "#t" : "#f")
		if (match(line, /--sport[[:space:]]+(![[:space:]]+)?([^[:space:]:]+)(:([^[:space:]:]+))?/, list) > 0)
			rule = rule sprintf(" not_sport %s sport1 \"%s\" sport2 \"%s\"",
				(list[1] ~ /^!/) ? "#t" : "#f", list[2], list[4])
		if (match(line, /--dport[[:space:]]+(![[:space:]]+)?([^[:space:]:]+)?(:([^[:space:]:]+))?/, list) > 0)
			rule = rule sprintf(" not_dport %s dport1 \"%s\" dport2 \"%s\"",
				(list[1] ~ /^!/) ? "#t" : "#f", list[2], list[4])
		# if no flags should be set, use NONE
		if (match(line, /--tcp-flags[[:space:]]+(![[:space:]]+)?([^[:space:]]+)[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_tcp_flags %s check_syn %s check_syn1 %s check_ack %s check_ack1 %s check_fin %s check_fin1 %s check_rst %s check_rst1 %s check_urg %s check_urg1 %s check_psh %s check_psh1 %s",
			       (list[1] ~ /^!/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)SYN(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)SYN(,|$)/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)ACK(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)ACK(,|$)/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)FIN(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)FIN(,|$)/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)RST(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)RST(,|$)/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)URG(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)URG(,|$)/) ? "#t" : "#f",
			       (list[2] ~ /(^|,)PSH(,|$)/) ? "#t" : "#f",
			       (list[3] ~ /(^|,)PSH(,|$)/) ? "#t" : "#f")
		if (match(line, /--tcp-option[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_tcp_option %s tcp_option \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])
		if (match(line, /--icmp-type[[:space:]]+(![[:space:]]+)?([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" not_icmp_type %s icmp_type \"%s\"", (list[1] ~ /^!/) ? "#t" : "#f", list[2])

		if (match(line, /-j[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" fwaction \"%s\"", list[1])
		if (match(line, /--log-level[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" log_level \"%s\"", list[1])
		if (match(line, /--log-prefix[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" log_prefix \"%s\"", list[1])
		if (line ~ /--log-tcp-sequence(\y|$)/)
			rule = rule sprintf(" log_tcp_sequence #t")
		if (line ~ /--log-tcp-options(\y|$)/)
			rule = rule sprintf(" log_tcp_options #t")
		if (line ~ /--log-ip-options(\y|$)/)
			rule = rule sprintf(" log_ip_options #t")
		if (match(line, /--set-mark[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" set_mark \"%s\"", list[1])
		if (match(line, /--reject-with[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" reject_with \"%s\"", list[1])
		if (match(line, /--set-tos[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" set_tos \"%s\"", list[1])
		if (match(line, /--state[[:space:]]+([^[:space:]:]+)/, list) > 0)
			rule = rule sprintf(" check_invalid %s check_new %s check_established %s check_related %s",
			       (list[1] ~ /(^|,)INVALID(,|$)/) ? "#t" : "#f",
			       (list[1] ~ /(^|,)NEW(,|$)/) ? "#t" : "#f",
			       (list[1] ~ /(^|,)ESTABLISHED(,|$)/) ? "#t" : "#f",
			       (list[1] ~ /(^|,)RELATED(,|$)/) ? "#t" : "#f")
		if (match(line, /--ttl-set[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" ttl_set \"%s\"", list[1])
		if (match(line, /--ttl-dec[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" ttl_dec \"%s\"", list[1])
		if (match(line, /--ttl-inc[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" ttl_inc \"%s\"", list[1])

		if (match(line, /--to-source[[:space:]]+([^[:space:]:-]+)(-([^[:space:]:-]+))?(:([^[:space:]:]+)-([^[:space:]:]+))?/, list) > 0)
			rule = rule sprintf(" to_source1 \"%s\" to_source2 \"%s\" to_source3 \"%s\" to_source4 \"%s\"", list[1], list[3], list[5], list[6])
		if (match(line, /--to-destination[[:space:]]+([^[:space:]:-]+)(-([^[:space:]:-]+))?(:([^[:space:]:]+)-([^[:space:]:]+))?/, list) > 0)
			rule = rule sprintf(" to_dest1 \"%s\" to_dest2 \"%s\" to_dest3 \"%s\" to_dest4 \"%s\"", list[1], list[3], list[5], list[6])
		if (match(line, /--to-ports[[:space:]]+([^[:space:]-]+)(-([^[:space:]-]+))?/, list) > 0)
			rule = rule sprintf(" to_port1 \"%s\" to_port2 \"%s\"", list[1], list[3])

		if (match(line, /--datestart[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" datestart \"%s\"", list[1])
		else
			rule = rule " datestart \"YYYY:MM:DD:hh:mm:ss\""
		if (match(line, /--datestop[[:space:]]+([^[:space:]]+)/, list) > 0)
			rule = rule sprintf(" datestop \"%s\"", list[1])
		else
			rule = rule " datestop \"YYYY:MM:DD:hh:mm:ss\""
		return rule
}

# write chain rule
function iptables_write_rule(table, chain, hash, num, params,		file, cmd, tempfile, error, line, number, count, tmp) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/) {
			print line >> tempfile
			continue
		}
		if (line ~ /-P\y/) {
			print line >> tempfile
			continue
		}
		number++
		if (number == params["number"]) {
			print iptables_unparse_rule(params) >> tempfile
			number++
		}
		if (hash != hash(line)) {
			print line >> tempfile
			continue
		}
		count++
		if (count != num) {
			print line >> tempfile
			continue
		}
		number--
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	if (number < params["number"])
		print iptables_unparse_rule(params) >> tempfile
	close(file)
	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)
}

# unparse iptables rule
function iptables_unparse_rule(params,		line, states, state, masks, mask, comps, comp) {
	if (params["protocol"])
		line = "-p " (params["not_protocol"] == "#t" ? "! " : "" ) params["protocol"]
	if (params["source"])
		line = line " -s " (params["not_source"] == "#t" ? "! " : "" ) params["source"]
	if (params["iface_in"] && params["iface_in"] != "+")
		line = line " -i " (params["not_iface_in"] == "#t" ? "! " : "" ) params["iface_in"]
	if (params["destination"])
		line = line " -d " (params["not_destination"] == "#t" ? "! " : "" ) params["destination"]
	if (params["iface_out"] && params["iface_out"] != "+")
		line = line " -o " (params["not_iface_out"] == "#t" ? "! " : "" ) params["iface_out"]
	if (params["fragment"] == "#t")
		line = line (params["not_fragment"] == "#t" ? " !" : "" ) " -f"
	if (params["sport1"] || params["sport2"]) {
		line = line " --sport " (params["not_sport"] == "#t" ? "! " : "" )
		if (params["sport1"])
			line = line params["sport1"]
		if (params["sport2"])
			line = line ":" params["sport2"]
	}
	if (params["dport1"] || params["dport2"]) {
		line = line " --dport " (params["not_dport"] == "#t" ? "! " : "" )
		if (params["dport1"])
			line = line params["dport1"]
		if (params["dport2"])
			line = line ":" params["dport2"]
	}

	if (params["check_syn"] == "#t")
		 masks["SYN"] = "SYN"
	if (params["check_syn1"] == "#t")
		 comps["SYN"] = "SYN"
	if (params["check_ack"] == "#t")
		 masks["ACK"] = "ACK"
	if (params["check_ack1"] == "#t")
		 comps["ACK"] = "ACK"
	if (params["check_fin"] == "#t")
		 masks["FIN"] = "FIN"
	if (params["check_fin1"] == "#t")
		 comps["FIN"] = "FIN"
	if (params["check_rst"] == "#t")
		 masks["RST"] = "RST"
	if (params["check_rst1"] == "#t")
		 comps["RST"] = "RST"
	if (params["check_urg"] == "#t")
		 masks["URG"] = "URG"
	if (params["check_urg1"] == "#t")
		 comps["URG"] = "URG"
	if (params["check_psh"] == "#t")
		 masks["PSH"] = "PSH"
	if (params["check_psh1"] == "#t")
		 comps["PSH"] = "PSH"
	for (i in masks)
		if (mask)
			mask = mask "," i
		else
			mask = i
	for (i in comps)
		if (comp)
			comp = comp "," i
		else
			comp = i
	if (mask) {
		 line = line " --tcp-flags " mask
		 if (comp)
			 line = line " " comp
		 else
			 line = line " NONE"
	}

	if (params["tcp_option"])
		line = line " --tcp-option " (params["not_tcp_option"] == "#t" ? "! " : "" ) params["tcp_option"]
	if (params["icmp_type"])
		line = line " --icmp-type " (params["not_icmp_type"] == "#t" ? "! " : "" ) params["icmp_type"]

	if (params["fwaction"])
		line = line " -j " params["fwaction"]
	if (params["log_level"])
		line = line " --log-level " params["log_level"]
	if (params["log_prefix"])
		line = line " --log-prefix " params["log_prefix"]
	if (params["log_tcp_sequence"] == "#t")
		line = line " --log-tcp-sequence"
	if (params["log_tcp_options"] == "#t")
		line = line " --log-tcp-options"
	if (params["log_ip_options"] == "#t")
		line = line " --log-ip-options"
	if (params["set_mark"])
		line = line " --set-mark " params["set_mark"]
	if (params["reject_with"])
		line = line " --reject-with " params["reject_with"]
	if (params["set_tos"])
		line = line " --set-tos " params["set_tos"]
	
	if (params["check_invalid"] == "#t")
		 states["INVALID"] = "INVALID"
	if (params["check_new"] == "#t")
		 states["NEW"] = "NEW"
	if (params["check_established"] == "#t")
		 states["ESTABLISHED"] = "ESTABLISHED"
	if (params["check_related"] == "#t")
		 states["RELATED"] = "RELATED"
	for (i in states)
		if (state)
			state = state "," i
		else
			state = i
	if (state)
		 line = line " --state " state
	
	if (params["ttl_set"])
		line = line " --ttl-set " params["ttl_set"]
	if (params["ttl_dec"])
		line = line " --ttl-dec " params["ttl_dec"]
	if (params["ttl_inc"])
		line = line " --ttl-inc " params["ttl_inc"]


	if (params["to_source1"]) {
		line = line " --to-source " params["to_source1"]
		if (params["to_source2"])
			line = line "-" params["to_source2"]
		if (params["to_source3"]) {
			line = line ":" params["to_source3"]
			if (params["to_source4"])
				line = line "-" params["to_source4"]
		}
	}
	if (params["to_dest1"]) {
		line = line " --to-destination " params["to_dest1"]
		if (params["to_dest2"])
			line = line "-" params["to_dest2"]
		if (params["to_dest3"]) {
			line = line ":" params["to_dest3"]
			if (params["to_dest4"])
				line = line "-" params["to_dest4"]
		}
	}
	if (params["to_port1"]) {
		line = line " --to-ports " params["to_port1"]
		if (params["to_port2"])
			line = line "-" params["to_port2"]
	}

	if (params["datestart"] && params["datestart"] != "YYYY:MM:DD:hh:mm:ss")
		line = line " --datestart " params["datestart"]
	if (params["datestop"] && params["datestop"] != "YYYY:MM:DD:hh:mm:ss")
		line = line " --datestart " params["datestop"]
	return line
}

# delete chain rule
function iptables_delete_rule(table, chain, hash, num,		file, cmd, tempfile, error, line, count, tmp) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	file = TABLEDIR "/" table "/" chain
	while ((error = getline line <file) > 0) {
		if (hash != hash(line)) {
			print line >> tempfile
			continue
		}
		count++
		if (count != num)
			print line >> tempfile
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
	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,			list, cmd, line) {
	split(LANGUAGE, list, /:/)
	cmd = "LANGUAGE=\"" LANGUAGE "\" LANG=\"" list[1] ".UTF8\" gettext " TEXTDOMAIN " \"" 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 " number (label \"%s\")", N_("Number")
			printf " fwaction (label \"%s\")", N_("Action")
			printf " protocol (label \"%s\")", N_("Protocol")
			printf " source (label \"%s\" match \"^[[:digit:]./]+$\")", N_("From")
			printf " destination (label \"%s\" match \"^[[:digit:]./]+$\")", N_("To")
			printf " sport1 (label \"%s\" match \"^[[:digit:]]+$\")", N_("Source port 1")
			printf " sport2 (label \"%s\" match \"^[[:digit:]]+$\")", N_("Source port 2")
			printf " dport1 (label \"%s\" match \"^[[:digit:]]+$\")", N_("Destination port 1")
			printf " dport2 (label \"%s\" match \"^[[:digit:]]+$\")", N_("Destination port 2")
			printf " to_port1 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To port 1")
			printf " to_port2 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To port 2")
			printf " to_source1 (label \"%s\" match \"^[[:digit:]./]+$\")", N_("To source IP 1")
			printf " to_source2 (label \"%s\" match \"^[[:digit:]./]+$\")", N_("To source IP 2")
			printf " to_source3 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To source port 1")
			printf " to_source4 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To source port 2")
			printf " to_dest1 (label \"%s\" match \"^[[:digit:]./]+$\")", N_("To destination IP 1")
			printf " to_dest2 (label \"%s\" match \"^[[:digit:]./]+$\")", N_("To destination IP 2")
			printf " to_dest3 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To destination port 1")
			printf " to_dest4 (label \"%s\" match \"^[[:digit:]]+$\")", N_("To destination port 2")
			printf " datestart (match \"^(YYYY:MM:DD:hh:mm:ss|[[:digit:]:]+)$\" label \"%s\")", N_("Start date")
			printf " datestop (match \"^(YYYY:MM:DD:hh:mm:ss|[[:digit:]:]+)$\" label \"%s\")", N_("Stop date")
			printf " tcp_option (label \"%s\" match \"^[[:digit:]]+$\")", N_("TCP option")
			print  " ttl_set (label \"--ttl-set\" match \"^[[:digit:]]+$\")"
			print  " ttl_dec (label \"--ttl-dec\" match \"^[[:digit:]]+$\")"
			print  " ttl_inc (label \"--ttl-inc\" match \"^[[:digit:]]+$\")"
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)/, a) > 0) {
				printf " table (label \"%s\")", a[1]
				printf " chain (label \"%s\")", a[2]
			}
			else
				printf " chain (match \"[0-9a-z]+\" label \"%s\")", N_("Chain")
			print ")"
			break
		case "list":
			print "("
			switch (params["_objects"]) {
				# give some actions for system upper-case chains
				case /\/[A-Z]+\/policy$/:
					print "(\"ACCEPT\")"
					print "(\"DROP\")"
					print "(\"QUEUE\")"
					print "(\"RETURN\")"
					break
				# for others give none
				case /\/policy$/:
					printf ("(\"\" label \"%s\")", N_("None"))
					break
				case /\yrules$/:
					if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)\//, a) > 0)
						iptables_list_rules(a[1], a[2])
					break
				case /\ynumbers$/:
					if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)/, a) > 0)
						for (i = 1; i <= iptables_rule_numbers(a[1], a[2]); i++)
							printf "(\"%d\")", i
						if (params["_objects"] !~ /\yrules\y/)
							printf "(\"%d\")", i
					break
				case /\yifaces$/:
					# fix me
					printf ("(\"+\" label \"%s\")", N_("Any"))
					iptables_list_ifaces()
					break
				case /\ylog_level$/:
					# check syntax
					printf ("(\"\" label \"%s\")", N_("None"))
					print "(\"debug\")"
					print "(\"info\")"
					print "(\"notice\")"
					print "(\"warning\")"
					print "(\"error\")"
					print "(\"err\" label \"error\")"
					print "(\"crit\" label \"critical\")"
					print "(\"alert\")"
					print "(\"emerg\" label \"emergency\")"
					break
				case /\yreject_with$/:
					# check syntax
					printf ("(\"\" label \"%s\")", N_("None"))
					print "(\"icmp-port-unreachable\")"
					print "(\"icmp-net-unreachable\")"
					print "(\"icmp-host-unreachable\")"
					print "(\"icmp-proto-unreachable\")"
					print "(\"icmp-net-prohibited\")"
					print "(\"icmp-host-prohibited\")"
					print "(\"icmp-admin-prohibited\")"
					print "(\"tcp-reset\")"
					break
				case /\yset_tos$/:
					# check syntax
					printf ("(\"\" label \"%s\")", N_("None"))
					printf ("(\"0\" label \"%s\")", N_("0x00 - Normal-Service"))
					printf ("(\"2\" label \"%s\")", N_("0x02 - Minimize-Cost"))
					printf ("(\"4\" label \"%s\")", N_("0x04 - Maximize-Reliability"))
					printf ("(\"8\" label \"%s\")", N_("0x08 - Maximize-Throughput"))
					printf ("(\"16\" label \"%s\")", N_("0x10 - Minimize-Delay"))
					break
				case /\yicmp_type$/:
					# check syntax
					printf ("(\"\" label \"%s\")", N_("None"))
					print "(\"any\")"
					print "(\"echo-reply\" label \"echo-reply (pong)\")"
					print "(\"destination-unreachable\")"
					print "(\"network-unreachable\" label \"... network-unreachable\")"
					print "(\"host-unreachable\" label \"... host-unreachable\")"
					print "(\"protocol-unreachable\" label \"... protocol-unreachable\")"
					print "(\"port-unreachable\" label \"... port-unreachable\")"
					print "(\"fragmentation-needed\" label \"... fragmentation-needed\")"
					print "(\"source-route-failed\" label \"... source-route-failed\")"
					print "(\"network-unknown\" label \"... network-unknown\")"
					print "(\"host-unknown\" label \"... host-unknown\")"
					print "(\"network-prohibited\" label \"... network-prohibited\")"
					print "(\"host-prohibited\" label \"... host-prohibited\")"
					print "(\"TOS-network-unreachable\" label \"... TOS-network-unreachable\")"
					print "(\"TOS-host-unreachable\" label \"... TOS-host-unreachable\")"
					print "(\"communication-prohibited\" label \"... communication-prohibited\")"
					print "(\"host-precedence-violation\" label \"... host-precedence-violation\")"
					print "(\"precedence-cutoff\" label \"... precedence-cutoff\")"
					print "(\"source-quench\")"
					print "(\"redirect\")"
					print "(\"network-redirect\" label \"... network-redirect\")"
					print "(\"host-redirect\" label \"... host-redirect\")"
					print "(\"TOS-network-redirect\" label \"... TOS-network-redirect\")"
					print "(\"TOS-host-redirect\" label \"... TOS-host-redirect\")"
					print "(\"echo-request\" label \"echo-request (ping)\")"
					print "(\"router-advertisement\")"
					print "(\"router-solicitation\")"
					print "(\"time-exceeded\" label \"time-exceeded (ttl-exceeded)\")"
					print "(\"ttl-zero-during-transit\" label \"... ttl-zero-during-transit\")"
					print "(\"ttl-zero-during-reassembly\" label \"... ttl-zero-during-reassembly\")"
					print "(\"parameter-problem\")"
					print "(\"ip-header-bad\" label \"... ip-header-bad\")"
					print "(\"required-option-missing\" label \"... required-option-missing\")"
					print "(\"timestamp-request\")"
					print "(\"timestamp-reply\")"
					print "(\"address-mask-request\")"
					print "(\"address-mask-reply\")"
					break
				case /\yprotocol$/:
					printf ("(\"\" label \"%s\")", N_("None"))
					printf ("(\"all\" label \"%s\")", N_("All"))
					print "(\"tcp\")"
					print "(\"udp\")"
					print "(\"icmp\")"
					iptables_list_protocols()
					break
				case /\yfwaction$/:
					printf ("(\"\" label \"%s\")", N_("None"))
					print "(\"ACCEPT\")"
					print "(\"REJECT\")"
					print "(\"DROP\")"
					print "(\"QUEUE\")"
					print "(\"RETURN\")"

					print "(\"BALANCE\")"
					print "(\"CLASSIFY\")"
					print "(\"CLUSTERIP\")"
					print "(\"CONNMARK\")"
					print "(\"DNAT\")"
					print "(\"DSCP\")"
					print "(\"ECN\")"
					print "(\"LOG\")"
					print "(\"MARK\")"
					print "(\"MASQUERADE\")"
					print "(\"MIRROR\")"
					print "(\"NETMAP\")"
					print "(\"NOTRACK\")"
					print "(\"REDIRECT\")"
					print "(\"ROUTE\")"
					print "(\"SNAT\")"
					print "(\"TCPMSS\")"
					print "(\"TOS\")"
					print "(\"TRACE\")"
					print "(\"TTL\")"
					print "(\"ULOG\")"
					# end with chains
				case /^chains\y/:
					if (match(params["_objects"], /^chains\/([^/]+)/, a) > 0)
							iptables_list_chains(a[1])
					break
				default:
					iptables_list_ifaces()
			}
			print ")"
			break
		case "read":
			print "("
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)$/, a) > 0)
				printf "policy \"%s\" number \"%d\"",
					iptables_read_policy(a[1], a[2]),
					iptables_rule_numbers(a[1], a[2]) + 1
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)\/rules\/([[:digit:]]+);([[:digit:]]+)$/, a) > 0)
				iptables_read_rule(a[1], a[2], a[3], a[4])
			print ")"
			break
		case "write":
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)\/rules\/([[:digit:]]+);([[:digit:]]+)$/, a) > 0 && params["ok"]) {
				iptables_write_rule(a[1], a[2], a[3], a[4], params)
				break
			}
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)$/, a) > 0) {
				if (params["policy"]) {
					iptables_save_policy(a[1], a[2], params["policy"])
					break
				}
				else
					if (params["ok"]) {
						iptables_write_rule(a[1], a[2], 0, 1, params)
						break
					}
			}
			print "()"
			break
		case "new":
			switch (params["type"]) {
				case /^chains\y/:
					if (params["chain"]) {
						match(params["type"], /^chains\/(.*)$/, a)
						iptables_add_chain(a[1], params["chain"])
					}
					else
						print "()"
					break
				default:
					print "()"
			}
			break
		case "delete":
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)\/rules\/([[:digit:]]+);([[:digit:]]+)$/, a) > 0 && params["delete"] == "#t") {
				iptables_delete_rule(a[1], a[2], a[3], a[4])
					break
			}
			if (match(params["_objects"], /^chains\/([^/]+)\/([^/]+)$/, a) > 0 && params["delete"] == "#t") {
				iptables_delete_chain(a[1], a[2])
				break
			}
			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
}
