Send email from other address restriction check (SOLVED)

kopano

#1

Hello, it’s complex problem.
1:
by default Postfix from Univention allow to send email from every address for authenticated users.
It could be solved by
ucr set mail /postfix/smtpd/restrictions/recipient/20 = "reject_authenticated_sender_login_mismatch"

2:
but this is not a good solution if you use Kopano and you use smtp/imap configured mail clients.

Kopano adding additional custom schemas to ldap such as k4uUserSendAsPrivilege that allow user to send email as other user. It’s works only if you use web interface. Postfix has no checking scripts for this.

So if you implemented “reject_authenticated_sender_login_mismatch” to close the bug , you will not able to use "UserSendAsPrivilege " by smtp.

  1. There is univention script listfilter.py that checks if users allowed to send email to group. it works as filter for

smtpd_recipient_restrictions = check_policy_service unix:private/listfilter,

  1. Is there a solution for k4uUserSendAsPrivilege or i should write same script for it?

#2

Crosspost: https://forum.kopano.io/topic/1964/k4uusersendasprivilege-checking-for-postfix/

Edit: if you decide to write your own filter script you could consider to integrate it with the integration package: https://wiki.z-hub.io/display/K4U/Contributor+Guidelines


#5

i quick rewrited existing script , it’s works in test, but i need to google how to insert realuser and fake user in postfix pipe.

i will bakl tommorow


#7

Ok.

solved with new script.

Problems:

  1. Kopano web user are not sasl authenticated.

Due to this we should set these restrictions to postfix:

smtpd_recipient_restrictions = check_policy_service unix:private/listfilter,  # check maillist resctiction
        permit_mynetworks,  #allow web kopano send  no sasl authenticated emails.  Kopano has self check "sendas" restriction mechanism. 
	check_policy_service unix:private/testfilter,  # here our new filter !!
        permit_sasl_authenticated, 
        reject_unauth_destination,
        reject_unlisted_recipient
  1. In this position our script will check ALL emails , for example it will check incoming emails from externel mail server. And we have 2 options.
    a). don’t check emails from external mail servers and getting mailforget SPAM
    b) check external mail server too, but only if they trying to send from our email address.

i prefer b) , but you can switch to a) by pass_not_authenticated = True in script.

  1. i don’t know how kopano generate this file /etc/listfilter.secret, it generated by listfilter option, but if you use listfilter you don’t need care about it.

postfix configuration:

master.cf.local)

send_as_filter     unix  -       n       n       -       30       spawn user=listfilter argv=/usr/share/univention-mail-postfix/send_as_filter.py
    -b dc=your_ldap_domain,dc=local 

ucr set  mail/postfix/smtpd/restrictions/recipient/20="check_policy_service unix:private/send_as_filter"
univention-config-registry commit /etc/postfix/main.cf
univention-config-registry commit /etc/postfix/master.cf
service postfix reload

/usr/share/univention-mail-postfix/send_as_filter.py

#!/usr/bin/python2.7 -u




import univention.uldap
import optparse
import sys
import re
import traceback
import syslog
from ldap.filter import filter_format
from univention.config_registry import ConfigRegistry
import univention.admin.modules

usage = "help"
parser = optparse.OptionParser(usage=usage)
parser.add_option("-b", "--ldap_base", dest="ldap_base", help="ldap base")
parser.add_option("-s", "--sender", dest="sender", help="sender address (for use with -t)")
parser.add_option("-t", "--test", dest="test", help="test run", action="store_true", default=False)
options, args = parser.parse_args()

syslog.openlog(ident="kopano_send_as_filter", logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL)
ucr = ConfigRegistry()
ucr.load()
univention.admin.modules.update()
usersmod = univention.admin.modules.get("users/user")
_do_debug = True

# should we pass mails from externel mail servers without checking?  If this case we will have potential mailforged SPAM to us.
pass_not_authenticated = False


def debug(msg, *args):
	if _do_debug:
		msg = "kopano_send_as_filter: {}".format(msg % args)
		if options.test:
			sys.stderr.write("{}\n".format(msg))
		else:
			syslog.syslog(syslog.LOG_DEBUG, msg)


def listfilter(attrib):
	sender = attrib.get("sasl_username", None)
	send_as = attrib.get("sender", None)

	debug("sender=%r send_as=%r", sender, send_as)
	debug("attrib=%r", attrib)

	if pass_not_authenticated:
		if not sender:
			return "DUNNO not sasl_authenticated. pass_not_authenticated = True option is enabled"

	if not options.ldap_base:
		return "443 LDAP base not set."
	elif not send_as:
		return "REJECT Access denied for empty sender."
	else:
		try:
			ldap = univention.uldap.getMachineConnection(ldap_master=False, secret_file="/etc/listfilter.secret")

			user_dn = ""
			users_groups = []
			allowed_user_dns = []

			# try the ldap stuff, if that fails send email anyway
			# get send_as restriction
			ldap_attr = ["kopanoSendAsPrivilege"]
			ldap_filter = filter_format(
				'(&(mailPrimaryAddress=%s)(|(objectclass=kopano-user)))',
				(send_as,))
			result = ldap.search(base=options.ldap_base, filter=ldap_filter, attr=ldap_attr)

			if result:
				
				user_filter = filter_format(
                                        '(uid=%s)',
                                        (sender,)
                                )
				primarymail = ldap.search(base=options.ldap_base, filter=str(ldap_filter), attr=["mailPrimaryAddress"])
				primarymail = primarymail[0][1].values()[0][0]
				if primarymail == send_as:
				    return "DUNNO user is mail owner"
                                #debug("mail=%r, sender=%r", primarymail, send_as)
				
				# get allowed user dns
				for u in result[0][1].get("kopanoSendAsPrivilege", []):
					allowed_user_dns.append(u)

				# check if there are restrictions, check sender first
				if allowed_user_dns:
					debug("allowed_user_dns=%r", allowed_user_dns)
					if not sender:
						return "REJECT Access denied for not authenticated sender to send as user %s" % send_as 

					# get dn of sender
					user_filter = filter_format(
						'(uid=%s)',
						(sender,)
					)
					ldap_filter = usersmod.lookup_filter(user_filter)
					user_result = ldap.search(base=options.ldap_base, filter=str(ldap_filter), attr=["uidNumber"])
					if user_result:
						user_dn = user_result[0][1].values()[0][0]
						debug("user_dn=%r", user_dn)

						# check useruidNiumber in kopanoSendAsPrivilege
						if allowed_user_dns and \
								user_dn and \
								user_dn in allowed_user_dns:
							return "DUNNO allowed per user uidNumber"

					return "REJECT Access denied for %s to send as user  %s" % (sender, send_as)
				else:
					return "REJECT Access denied for %s to send as user  %s" % (sender, send_as)
			else:
				return "DUNNO %s is not our email address" % send_as
		except Exception:
			return "WARN Error with sender={} send_as={} attrib={}, traceback={}".format(
				sender, send_as, attrib, traceback.format_exc().replace("\n", " "))

# main
attr = {}

# testing
if options.test:
	_do_debug = True
	if not options.sender or not options.send_as:
		sys.stderr.write("sender and send_as are required\n")
		parser.print_help()
		sys.exit(1)
	attr["sender"] = options.sender
	attr["send_as"] = options.send_as
	action = listfilter(attr)
	print("action={}\n".format(action))
else:
	# read from stdin python -u is required for unbufferd streams
	while True:
		data = sys.stdin.readline()
		m = re.match(r'([^=]+)=(.*)\n', data)
		if m:
			attr[m.group(1).strip()] = m.group(2).strip()

		elif data == "\n":
			if attr.get("request", None) == "smtpd_access_policy":
				action = listfilter(attr)
				debug("action=%s", action)
				sys.stdout.write("action=%s\n\n" % action)
			else:
				sys.stderr.write("unknown action in %s\n" % attr)
				sys.stdout.write("defer_if_permit Service temporarily unavailable\n")
				syslog.syslog(syslog.LOG_ERR, "unknown action in '{}', exiting.".format(attr))
				sys.exit(1)
			attr = {}
		elif data == "":
			# Postfix telling us to shutdown (max_idle).
			debug("shutting down (max_idle)")
			sys.exit(0)
		else:
			syslog.syslog(syslog.LOG_ERR, "received bad data: '{}', exiting.".format(data))
			sys.exit(1)

#8

https://jira.z-hub.io/projects/KUCS/issues/KUCS-40


#9

This looks great! FYI, there is a also a feature request for UCS: http://forge.univention.org/bugzilla/show_bug.cgi?id=40609