#!/bin/awk -f

BEGIN {
	ALIASES = "/etc/postfix/aliases"
	ALWAYS_ACCEPT = "/etc/postfix/always_accept"
	RBL_DOMAINS = "/etc/postfix/rbl_domains"
	ACCESS_PREFIX = "/etc/postfix/"
	ACCESS_SUFFIX = "_access"
	POSTCONF = "/usr/sbin/postconf"
	POSTMAP = "/usr/sbin/postmap"
	NEWALIASES = "/usr/bin/newaliases"
	SERVICE = "/sbin/service"
	# split input fields by a colon
	FS=":"
	# set default language
	LANGUAGE="en_US.UTF8"
	# set translation domain
	TEXTDOMAIN = "alterator-postfix-restrictions"
	readmsg=0
	LOGFILE="/var/log/configd.log"
}

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

# remove leading and trailing spaces
function trim(arg) {
	gsub(/^[[:space:]]+/,"", arg)
	gsub(/[[:space:]]+$/,"", arg)
	return arg
}

# search a regular expression in a file
function grep(regex, file,		line) {
	while ((error = getline line <file) > 0)
		if (line ~ regex) {
			close(file)
			return 1
		}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
	return 0
}

# quote name/value pair
function wrap2(name, param, value) {
	return "(\"" name "\" " param " \"" value "\")"
}

# get Postfix config variable
function postconf_read(param,		cmd, ret) {
	cmd = POSTCONF " -h " param
	cmd | getline ret
	close(cmd)
	return ret
}

# write Postfix config variable
function postconf_write(name, value,		cmd) {
	cmd = POSTCONF " -e " name "=" value
	system(cmd)
}


# check Postfix acces list
function postfix_check (list,		option, type, file, value, regex) {
	option = "smtpd_" list "_restrictions"
	type = "check_" list "_access"
	file = "cdb:/etc/postfix/" list "_access"

	value = postconf_read(option)
	regex = "[[:space:]]*" type " " file "[[:space:]]*,"
	if (value ~ regex)
		return 1
	else
		return 0
}

# write Postfix access list directive
function postconf_access(params, list,	option, type, file, enable, value, extra, cmd) {
	option = "smtpd_" list "_restrictions"
	type = "check_" list "_access"
	file = "cdb:/etc/postfix/" list "_access"

	enable = (params[type] == "#t") ? 1 : 0
	# if parameter is turned on and it should be,
	# then just exit
	# the same if not/souldn't
	if (!xor(postfix_check(list), enable))
		return

	value = postconf_read(option)
	if (value == "")
		value = "permit"
	extra = type " " file ", "
	if (list != "recipient")
		extra = "check_recipient_access cdb:" ALWAYS_ACCEPT ", " extra
	switch (enable) {
		case 1:
			value = extra value
			cmd = POSTCONF " -e " option "=\"" value "\""
			system(cmd)
			break
		case 0:
			gsub(extra, "", value)
			cmd = POSTCONF " -e " option "=\"" value "\""
			system(cmd)
			break
		default:
	}
}

# write RBL domains to the file and set smtpd_client_restrictions parameter
function write_rbl (domains,		count, list, rbl, i, value, cmd) {
	gsub(/[[:space:]]*,[[:space:]]*/, "\n", domains)
	gsub(/[[:space:]]+/, "\n", domains)
	print domains > RBL_DOMAINS
	close(RBL_DOMAINS)

	count = split(domains, list, /\n/)
	rbl = ""
	for (i = 1; i <= count; i++) {
		# skip commented or empty lines
		if (list[i] ~ /^#/ || list[i] == "")
			continue
		rbl = rbl "reject_rbl_client " list[i] ", "
	}
	# get current parameter value
	value = postconf_read("smtpd_client_restrictions")
	# set default
	if (value == "")
		value = "permit"
	
	count = split(value, list, /[[:space:]]*,[[:space:]]*/)
	# start parameter with always accepted destinations
	value = "check_recipient_access cdb:" ALWAYS_ACCEPT ", "
	for (i = 1; i < count; i++) {
		# skip previous reject_rbl_client directives
		# or always_accept file
		# or empty parts
		if (list[i] ~ /reject_rbl_client/ || list[i] ~ "cdb:" ALWAYS_ACCEPT || list[i] == "")
			continue
		value = value list[i] ", "
	}
	# add new RBL directive list and tail of current value
	# (probably "permit")
	value = value rbl list[count]

	# write new parameter value	
	cmd = POSTCONF " -e smtpd_client_restrictions=\"" value "\""
	system(cmd)
}

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

# filter name/value pair list from an aliases file
function list_aliases(aliases,		error, line, alias, name, value) {
	while ((error = getline line <aliases) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		if (line ~ /^[[:space:]]/) {
			value = value " " trim(line)
			continue
		}
		if (line ~ /^[^[:space:]]+/) {
			if (name != "")
				print wrap2(name, "value", value)
			if (split(line, alias, /:/) > 1) {
				name = trim(alias[1])
				value = trim(alias[2])
			}
		}
	}
	if (error != -1)
		print wrap2(name, "value", value)
	else
		debug("Error reading file '" aliases "'", ERRNO)
	close(aliases)
}

function list_always_accept(file,	error, line, list) {
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		split(line, list, /[[:space:]]+/)
		print "(\"" list[1] "\")"
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
}

function list_access(file,	error, line, list) {
	while ((error = getline line <file) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/)
			continue
		split(line, list, /[[:space:]]+/)
		print "(\"" list[1] "\" address \"" list[1] "\" reaction \"" list[2] "\")"
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
}

# print file contents skipping empty lines
function cat_file(file,		error, line) {
	while ((error = getline line <file) > 0) {
		if (line ~ /^[[:space:]]*$/)
			continue
		print line
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
}

function print_constraints() {
	printf " mailbox_size_limit (label \"%s\")", N_("Maximum mailbox size")
	printf " message_size_limit (label \"%s\")", N_("Maximum size of a message")
	printf " check_recipient_access (label \"%s\" default #f)", N_("Filter out recipients")
	printf " check_sender_access (label \"%s\" default #f)", N_("Filter out senders")
	printf " check_client_access (label \"%s\" default #f)", N_("Filter out clients")
	printf " rbl (label \"%s\")", N_("Check in domains")
	printf " name (label \"%s\")", N_("Name")
	printf " value (label \"%s\")", N_("Value")
	printf " address (label \"%s\")", N_("Address")
	printf " reaction (label \"%s\")", N_("Reaction")
	printf " relayhost (label \"%s\")", N_("Destination of non-local mail")
}

# remove alias from file
function delete_alias(aliases, name,	cmd, tempfile, todelete, regex, error, line) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	todelete = 0
	regex = "^" name ":"
	while ((error = getline line <aliases) > 0) {
		if (line ~ /^#/ || line ~ /^[[:space:]]*$/) {
			print line >> tempfile
			continue
		}
		if (line ~ /^[[:space:]]/) {
			if (!todelete)
				print line >> tempfile
			continue
		}
		if (line ~ regex) {
			todelete = 1
			continue
		}
		todelete = 0
		print line >> tempfile
	}
	if (error == -1)
		debug("Error reading file '" aliases "'", ERRNO)
	close(aliases)
	close(tempfile)
	system("cat " tempfile " > " aliases)
	system("rm -f " tempfile)
}

# remove access map from file
function delete_map(file, regex,	cmd, tempfile, error, line) {
	cmd = "mktemp"
	cmd | getline tempfile
	close(cmd)

	while ((error = getline line <file) > 0) {
		if (line ~ regex)
			continue
		print line >> tempfile
	}
	if (error == -1)
		debug("Error reading file '" file "'", ERRNO)
	close(file)
	close(tempfile)

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

# 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 "("
			print_constraints()
			print ")"
			break
		case "list":
			print "("
			switch (params["_objects"]) {
				case /^alias\y/:
					list_aliases(ALIASES)
					break
				case /^reaction\y/:
					printf "(\"OK\" label \"%s\")", N_("OK")
					printf "(\"Reject Blacklisted\" label \"%s\")", N_("Reject")
					break
				case /^always\y/:
					list_always_accept(ALWAYS_ACCEPT)
					break
				case /^recipient\y/:
				case /^sender\y/:
				case /^client\y/:
					match(params["_objects"], /^([^/]+)/, a)
					file = ACCESS_PREFIX a[1] ACCESS_SUFFIX
					list_access(file)
					break
				default:
					#print "#f"
			}
			print ")"
			break
		case "read":
			print "("
			printf " relayhost \"%s\"",  postconf_read("relayhost")
			printf " mailbox_size_limit \"%d\"", (postconf_read("mailbox_size_limit")/1048576)
			printf " message_size_limit \"%d\"", (postconf_read("message_size_limit")/1048576)
			printf " check_recipient_access %s", postfix_check("recipient") ? "#t" : "#f"
			printf " check_sender_access %s", postfix_check("sender") ? "#t" : "#f"
			printf " check_client_access %s", postfix_check("client") ? "#t" : "#f"
			print " rbl \""
			cat_file(RBL_DOMAINS)
			print "\""
			print ")"
			break
		case "write":
			if (params["mailbox_size_limit"] != "")
				postconf_write("mailbox_size_limit", params["mailbox_size_limit"]*1048576)
			if (params["message_size_limit"] != "")
				postconf_write("message_size_limit", params["message_size_limit"]*1048576)
			if (params["check_recipient_access"] != "")
				postconf_access(params, "recipient")
			if (params["check_sender_access"] != "")
				postconf_access(params, "sender")
			if (params["check_client_access"] != "")
				postconf_access(params, "client")
			write_rbl(params["rbl"])
			postconf_write("relayhost", params["relayhost"])
			system(SERVICE " postfix reload &> /dev/null")
			print "()"
			break
		case "new":
			switch (params["type"]) {
				case "alias":
					if (params["name"] != "" && params["value"] != "")
						if (!grep("^" params["name"] ":", ALIASES)) {
							print params["name"] ": " params["value"] >> ALIASES
							fflush(ALIASES)
							system(NEWALIASES " &> /dev/null")
							print "()"
						}
						else
							printf "(error \"%s\")", N_("Such alias already exists")
					break
				case "always":
					if (params["name"] != "")
						if (!grep("^" params["name"], ALWAYS_ACCEPT)) {
							print params["name"] " OK" >> ALWAYS_ACCEPT
							fflush(ALWAYS_ACCEPT)
							system(POSTMAP " " ALWAYS_ACCEPT " &> /dev/null")
							print "()"
						}
						else
							printf "(error \"%s\")", N_("Such address is already specified")
					break
				case "recipient":
				case "sender":
				case "client":
					file = ACCESS_PREFIX params["type"] ACCESS_SUFFIX
					if (params["address"] != "" && params["reaction"])
						if (!grep("^" params["address"], file)) {
							print params["address"] " " params["reaction"] >> file
							fflush(file)
							system(POSTMAP " " file " &> /dev/null")
							print "()"
						}
						else
							printf "(error \"%s\")", N_("Such address is already specified")
					break
				default:
					print "()"
			}
			break
		case "delete":
			switch (params["_objects"]) {
				case /^alias\y/:
					match(params["_objects"], /^alias\/(.*)$/, a)
					delete_alias(ALIASES, a[1])
					system(NEWALIASES " &> /dev/null")
					break
				case /^always\y/:
					match(params["_objects"], /^always\/(.*)$/, a)
					delete_map(ALWAYS_ACCEPT, "^" a[1])
					system(POSTMAP " " ALWAYS_ACCEPT " &> /dev/null")
					break
				case /^recipient\y/:
				case /^sender\y/:
				case /^client\y/:
					match(params["_objects"], /^([^/]+)\/(.*)$/, a)
					file = ACCESS_PREFIX a[1] ACCESS_SUFFIX
					delete_map(file, "^" a[2])
					system(POSTMAP " " file " &> /dev/null")
					break
				default:
			}
			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
}

