summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authorJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2011-11-09 11:56:21 +0000
committerJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2011-11-09 11:56:21 +0000
commit93f84a5d10f0b9fd64a840631832271f122034d7 (patch)
tree4c69e15fa479615035b9d507950264ec584d0780 /bin
parentb482086e4c42012a7651b8bdf590ee35c3dad909 (diff)
downloadpykolab-93f84a5d10f0b9fd64a840631832271f122034d7.tar.gz
Refactor kolab_smtp_access_policy to take into account subsequent access policy requests, and query databases only after the complete policy request has been received.
Diffstat (limited to 'bin')
-rw-r--r--[-rwxr-xr-x]bin/kolab_smtp_access_policy.py1668
1 files changed, 917 insertions, 751 deletions
diff --git a/bin/kolab_smtp_access_policy.py b/bin/kolab_smtp_access_policy.py
index 61928ea..87c4268 100755..100644
--- a/bin/kolab_smtp_access_policy.py
+++ b/bin/kolab_smtp_access_policy.py
@@ -4,18 +4,18 @@
#
# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
#
-# 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; version 3 or, at your option, any later version
+# 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; version 3 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 Library General Public License for more details.
+# 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 Library General Public License for more
+# details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+# Place - Suite 330, Boston, MA 02111-1307, USA.
#
import datetime
@@ -53,8 +53,8 @@ sys.path.append('../..')
import pykolab
+from pykolab import utils
from pykolab.auth import Auth
-from pykolab.constants import KOLAB_LIB_PATH
from pykolab.translate import _
# TODO: Figure out how to make our logger do some syslogging as well.
@@ -69,7 +69,7 @@ conf = pykolab.getConf()
auth = Auth()
#
-# Caching routines using MySQL-python.
+# Caching routines using SQLAlchemy.
#
# If creating the cache fails, we continue without any caching, significantly
# increasing the load on LDAP.
@@ -150,783 +150,1046 @@ class Statistic(object):
mapper(Statistic, statistic_table)
-def cache_cleanup():
- if not cache == True:
- return
+class PolicyRequest(object):
+ email_address_keys = [ 'sender', 'recipient' ]
+ recipients = []
- log.debug(_("Cleaning up the cache"), level=8)
- session.query(
- PolicyResult
- ).filter(
- PolicyResult.created < ((int)(time.time()) - cache_expire)
- ).delete()
+ sasl_domain = None
+ sasl_user = None
+ sender_domain = None
+ sender_user = None
-def cache_init():
- global cache, cache_expire, session
+ sasl_user_uses_alias = False
+ sasl_user_is_delegate = False
- if conf.has_section('kolab_smtp_access_policy'):
- if conf.has_option('kolab_smtp_access_policy', 'uri'):
- cache_uri = conf.get('kolab_smtp_access_policy', 'uri')
- cache = True
- if conf.has_option('kolab_smtp_access_policy', 'retention'):
- cache_expire = (int)(conf.get('kolab_smtp_access_policy', 'retention'))
- else:
- return False
- else:
- return False
+ def __init__(self, policy_request={}):
+ """
+ Creates a new policy request object. Pass it a policy_request
+ dictionary as described in the Postfix documentation on:
- engine = create_engine(cache_uri, echo=True)
+ http://www.postfix.org/SMTPD_POLICY_README.html
+ """
+ for key in policy_request.keys():
- try:
- metadata.create_all(engine)
- except sqlalchemy.exc.OperationalError, e:
- log.error(_("Operational Error in caching: %s" %(e)))
- return False
-
- Session = sessionmaker(bind=engine)
- session = Session()
+ # Normalize email addresses (they may contain recipient delimiters)
+ if key in self.email_address_keys:
+ policy_request[key] = normalize_address(policy_request[key])
- return cache
+ if not key == 'recipient':
+ if policy_request[key] == '':
+ setattr(self, key, None)
+ else:
+ setattr(self, key, policy_request[key])
-def cache_select(sender, recipient, function, sasl_username='',sasl_sender=''):
- if not cache == True:
- return None
+ else:
+ self.recipients.append(policy_request['recipient'])
- return session.query(
- PolicyResult
- ).filter_by(
- key=function,
- sender=sender,
- recipient=recipient,
- sasl_username=sasl_username,
- sasl_sender=sasl_sender
- ).filter(
- PolicyResult.created >= ((int)(time.time()) - cache_expire)
- ).first()
+ def add_request(self, policy_request={}):
+ """
+ Add subsequent policy requests to the existing policy request.
-def cache_insert(sender, recipient, function, result, sasl_username='',sasl_sender=''):
- if not cache == True:
- return []
+ All data in the request should be the same as the initial policy
+ request, but for the recipient - with destination limits set over
+ 1, Postfix may attempt to deliver messages to more then one
+ recipient during a single delivery attempt, and during submission,
+ the policy will receive one policy request per recipient.
+ """
- log.debug(_("Caching the policy result with timestamp %d") %((int)(time.time())), level=8)
+ # Check instance. Not sure what to do if the instance is not the same.
+ if hasattr(self, 'instance'):
+ if not policy_request['instance'] == self.instance:
+ # TODO: We need to empty our pockets
+ pass
- cache_cleanup()
+ log.debug(
+ _("Adding policy request to instance %s") %(self.instance),
+ level=8
+ )
- session.add(
- PolicyResult(
- key=function,
- value=result,
- sender=sender,
- recipient=recipient,
- sasl_username=sasl_username,
- sasl_sender=sasl_sender
+ # Normalize email addresses (they may contain recipient delimiters)
+ if policy_request.has_key('recipient'):
+ policy_request['recipient'] = normalize_address(
+ policy_request['recipient']
)
- )
- if result == 1:
- if not sasl_username == "":
- stat_update(sasl_username, recipient)
- else:
- stat_update(sender, recipient)
+ self.recipients.append(policy_request['recipient'])
- session.commit()
+ def parse_policy(self, _subject, _object, policy):
+ """
+ Parse policy to apply on _subject, for object _object.
-def stat_update(sender, recipient):
- if not cache == True:
- return
+ The policy is a list of rules.
- statistic = session.query(
- Statistic
- ).filter_by(
- sender=parse_address(sender),
- recipient=parse_address(recipient),
- date=datetime.date.today()
- ).first()
+ The _subject is a sender for kolabAllowSMTPRecipient checks, and
+ a recipient for kolabAllowSMTPSender checks.
- if not statistic == None:
- statistic.count += 1
- else:
- session.add(
- Statistic(
- sender=parse_address(sender),
- recipient=parse_address(recipient),
- date=datetime.date.today(),
- count=1
- )
- )
+ The _object is a recipient for kolabAllowSMTPRecipient checks, and
+ a sender for kolabAllowSMTPSender checks.
+ """
- session.commit()
+ special_rule_values = {
+ '$mydomains': expand_mydomains
+ }
-def defer_if_permit(message, policy_request=None):
- log.info(_("Returning action DEFER_IF_PERMIT: %s") %(message))
- print "action=DEFER_IF_PERMIT %s\n\n" %(message)
+ rules = { 'allow': [], 'deny': [] }
-def dunno(message, policy_request=None):
- log.info(_("Returning action DUNNO: %s") %(message))
- print "action=DUNNO %s\n\n" %(message)
+ if isinstance(policy, basestring):
+ policy = [policy]
-def hold(message, policy_request=None):
- log.info(_("Returning action HOLD: %s") %(message))
- print "action=HOLD %s\n\n" %(message)
+ for rule in policy:
+ if rule in special_rule_values.keys():
+ special_rules = special_rule_values[rule]()
+ if rule.startswith("-"):
+ rules['deny'].extend(special_rules)
+ else:
+ rules['allow'].extend(special_rules)
-def permit(message, policy_request=None):
- log.info(_("Returning action PERMIT: %s") %(message))
- print "action=PERMIT\n\n"
+ continue
-def reject(message, policy_request=None):
- log.info(_("Returning action REJECT: %s") %(message))
- print "action=REJECT %s\n\n" %(message)
+ try:
+ # See if the value is an LDAP DN
+ ldap_dn = []
+ if rule.startswith("-"):
+ _prefix = '-'
+ _rule = rule[1:]
+ else:
+ _prefix = ''
+ _rule = rule
-def parse_address(email_address):
- """
- Parse an address; Strip off anything after a recipient delimiter.
- """
+ import ldap.dn
- # TODO: Recipient delimiter is configurable.
- if len(email_address.split("+")) > 1:
- # Take the first part split by recipient delimiter and the last part
- # split by '@'.
- return "%s@%s" %(
- email_address.split("+")[0],
- # TODO: Under some conditions, the recipient may not be fully
- # qualified. We'll cross that bridge when we get there, though.
- email_address.split('@')[1]
- )
- else:
- return email_address
+ ldap_dn = ldap.dn.explode_dn(_rule)
+
+ except ldap.DECODING_ERROR:
+ pass
-def parse_policy(sender, recipient, policy):
+ if len(ldap_dn) > 0:
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
+ )
- # TODO: A future feature is to allow special values to be expanded
- #special_rule_values = {
- # '$mydomains': 'expand_mydomains'
- # ]
+ rule_subject = auth.get_user_attributes(
+ self.sasl_domain,
+ { 'dn': rule },
+ search_attrs + [ 'objectclass' ]
+ )
- rules = { 'allow': [], 'deny': [] }
+ for search_attr in search_attrs:
+ if rule_subject.has_key(search_attr):
+ if isinstance(rule_subject[search_attr], basestring):
+ if _prefix == '-':
+ rules['deny'].append(rule_subject[search_attr])
+ else:
+ rules['allow'].append(rule_subject[search_attr])
+ else:
+ for value in rule_subject[search_attr]:
+ if _prefix == '-':
+ rules['deny'].append(value)
+ else:
+ rules['allow'].append(value)
- for rule in policy:
- if rule.startswith("-"):
- rules['deny'].append(rule[1:])
- else:
- rules['allow'].append(rule)
-
- #print "From:", sender, "To:", recipient, "Rules:", rules
-
- allowed = False
- for rule in rules['allow']:
- # TODO: Example implementation of getting the special values to expand.
- # Note that the append works to extend the for loop.
- #if rule in special_rule_values:
- ## TODO: Expand the special rule value and do something
- ## intelligent.
- #if rule == '$mydomains':
- #mydomains = auth.list_domains()
- #for mydomain in mydomains:
- #rules['allow'].append(mydomain)
-
- #continue
-
- deny_override = False
- if recipient.endswith(rule):
- #print "Matched allow rule:", rule
- for deny_rule in rules['deny']:
- if deny_rule.endswith(rule):
- deny_override = True
-
- if not deny_override:
- allowed = True
-
- denied = False
- for rule in rules['deny']:
- allow_override = False
- if recipient.endswith(rule):
- #print "Matched deny rule:", rule
- if not allowed:
- denied = True
- continue
else:
- for allow_rule in rules['allow']:
- if allow_rule.endswith(rule):
- allow_override = True
+ if rule.startswith("-"):
+ rules['deny'].append(rule[1:])
+ else:
+ rules['allow'].append(rule)
- if not allow_override:
- denied = True
+ allowed = False
+ for rule in rules['allow']:
+ deny_override = False
- if not denied:
- allowed = True
+ if _object.endswith(rule):
+ for deny_rule in rules['deny']:
+ if deny_rule.endswith(rule):
+ deny_override = True
- return allowed
+ if not deny_override:
+ allowed = True
-def read_request_input():
- """
- Read a single policy request from sys.stdin, and return a dictionary
- containing the request.
- """
+ denied = False
- policy_request = {}
+ for rule in rules['deny']:
+ allow_override = False
+ if _object.endswith(rule):
+ if not allowed:
+ denied = True
+ continue
+ else:
+ for allow_rule in rules['allow']:
+ if allow_rule.endswith(rule):
+ allow_override = True
- end_of_request = False
- while not end_of_request:
- request_line = sys.stdin.readline().strip()
- if request_line == '':
- end_of_request = True
- else:
- policy_request[request_line.split('=')[0]] = \
- '='.join(request_line.split('=')[1:]).lower()
+ if not allow_override:
+ denied = True
- return policy_request
+ if not denied:
+ allowed = True
-def verify_alias(policy_request, sender_domain, sender_user):
- sender_uses_alias = None
+ return allowed
- search_attrs = conf.get_list(
- 'kolab_smtp_access_policy',
- 'address_search_attrs'
- )
+ def verify_alias(self):
+ """
+ Verify whether the user authenticated for this policy request is
+ using an alias of its primary authentication ID / attribute.
- print search_attrs
+ John.Doe@example.org (mail) for example could be sending with
+ envelope sender jdoe@example.org (mailAlternateAddress, alias).
+ """
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
+ )
- sender_aliases = auth.get_user_attributes(
- sender_domain,
- sender_user,
- search_attrs
- )
+ # Catch a user using one of its own alias addresses.
+ for search_attr in search_attrs:
+ if self.sasl_user.has_key(search_attr):
+ if isinstance(self.sasl_user[search_attr], list):
+ if self.sender in self.sasl_user[search_attr]:
+ return True
+ elif self.sasl_user[search_attr] == self.sender:
+ return True
- # We get back a normalized dictionary
- for key in sender_aliases.keys():
- if type(sender_aliases[key]) == list:
- if policy_request['sender'] in sender_aliases[key]:
- sender_uses_alias = True
- break
- else:
- if policy_request['sender'] == sender_aliases[key]:
- sender_uses_alias = True
- break
+ return False
- return sender_uses_alias
+ def verify_authenticity(self):
+ """
+ Verify that the SASL username or lack thereof corresponds with
+ allowing or disallowing authenticated users.
-def verify_delegate(policy_request, sender_domain, sender_user):
- """
- Use the information passed along to determine whether the authenticated
- user can send on behalf of the envelope sender, using the kolabDelegate
- attribute value on the envelope sender's LDAP object.
+ If an SASL username is supplied, use it to obtain the authentication
+ database user object including all attributes we may find ourselves
+ interested in.
+ """
- Returns True in case the user is a delegate of the sender, and False in
- case the user is NOT a delegate of the sender.
- """
-
- sender_is_delegate = None
+ if self.sasl_username == None:
+ if not conf.allow_unauthenticated:
+ reject(_("Unauthorized access not allowed"))
+ else:
+ # If unauthenticated is allowed, I have nothing to do here.
+ return True
- # TODO: Whether or not a domain name is in the sasl_username depends on
- # whether or not a default realm is specified elsewhere. In other words,
- # only attempt to do this and fall back to the primary_domain configured
- # for Kolab.
- sasl_domain = policy_request['sasl_username'].split('@')[1]
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
+ )
- sender_delegates = auth.get_user_attribute(
- sender_domain,
- sender_user,
- 'kolabDelegate'
- )
+ # If we have an sasl_username, find the user object in the
+ # authentication database, along with the attributes we are
+ # interested in.
+ if self.sasl_domain == None:
+ if len(self.sasl_username.split('@')) > 1:
+ self.sasl_domain = self.sasl_username.split('@')[1]
+ else:
+ self.sasl_domain = conf.get('kolab', 'primary_domain')
- if sender_delegates == None:
- # No delegates for this sender could be found. The user is definitely
- # NOT a delegate of the sender.
- log.warning(
- _("User %s attempted to use envelope sender address %s without " + \
- "authorization") %(
- policy_request["sasl_username"],
- policy_request["sender"]
+ self.sasl_user = {
+ 'dn': auth.find_user(
+ search_attrs,
+ self.sasl_username,
+ domain=self.sasl_domain
)
- )
+ }
- # Got a final answer here, do the cachking thing.
- if not cache == False:
- result = cache_select(
- sender=policy_request['sender'],
- recipient=policy_request['recipient'],
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender'],
- function='verify_sender'
+ if not self.sasl_user['dn']:
+ # Got a final answer here, do the caching thing.
+ cache_update(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ result=(int)(False),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
)
- if result == None:
- record_id = cache_insert(
- policy_request['sender'],
- policy_request['recipient'],
- 'verify_sender',
- (int)(False),
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender']
- )
+ reject(
+ _("Could not find envelope sender user %s") %(
+ self.sasl_username
+ )
+ )
- sender_is_delegate = False
+ attrs = search_attrs
+ attrs.extend(
+ [
+ 'kolabAllowSMTPRecipient',
+ 'kolabAllowSMTPSender'
+ ]
+ )
- else:
- # See if we can match the value of the envelope sender delegates to
- # the actual sender sasl_username
- sasl_user = {
+ user_attrs = auth.get_user_attributes(
+ self.sasl_domain,
+ self.sasl_user,
+ attrs
+ )
+
+ user_attrs['dn'] = self.sasl_user['dn']
+ self.sasl_user = utils.normalize(user_attrs)
+
+ def verify_delegate(self):
+ """
+ Verify whether the authenticated user is a delegate of the envelope
+ sender.
+ """
+
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
+ )
+
+ if self.sender_domain == None:
+ if len(self.sender.split('@')) > 1:
+ self.sender_domain = self.sender.split('@')[1]
+ else:
+ self.sender_domain = conf.get('kolab', 'primary_domain')
+
+ if self.sender == self.sasl_username:
+ return
+
+ self.sender_user = {
'dn': auth.find_user(
- # TODO: Use the configured cyrus-sasl result attribute.
- 'mail',
- parse_address(policy_request['sasl_username']),
- domain=sasl_domain
+ search_attrs,
+ self.sender,
+ domain=self.sender_domain
)
}
- # Possible values for the kolabDelegate attribute are:
- # a 'uid', a 'dn'.
- sasl_user['uid'] = auth.get_user_attribute(
- sasl_domain,
- sasl_user,
- 'uid'
+ if not self.sender_user['dn']:
+ cache_update(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ result=(int)(False),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
+
+ reject(_("Could not find envelope sender user %s") %(self.sender))
+
+ attrs = search_attrs
+ attrs.extend(
+ [
+ 'kolabAllowSMTPRecipient',
+ 'kolabAllowSMTPSender',
+ 'kolabDelegate'
+ ]
)
- if not type(sender_delegates) == list:
- sender_delegates = [ sender_delegates ]
+ user_attrs = auth.get_user_attributes(
+ self.sender_domain,
+ self.sender_user,
+ attrs
+ )
- for sender_delegate in sender_delegates:
- if sasl_user['dn'] == sender_delegate:
- log.debug(
- _("Found user %s to be a valid delegate user of %s") %(
- policy_request["sasl_username"],
- policy_request["sender"]
- ),
- level=8
- )
+ user_attrs['dn'] = self.sender_user['dn']
+ self.sender_user = utils.normalize(user_attrs)
- sender_is_delegate = True
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
+ )
- elif sasl_user['uid'] == sender_delegate:
- log.debug(
- _("Found user %s to be a valid delegate user of %s") %(
- policy_request["sasl_username"],
- policy_request["sender"]
- ),
- level=8
+ if not self.sender_user.has_key('kolabdelegate'):
+ reject(
+ _("%s is unauthorized to send on behalf of %s") %(
+ self.sasl_user['dn'],
+ self.sender_user['dn']
+ )
+ )
+
+ elif self.sender_user['kolabdelegate'] == None:
+ # No delegates for this sender could be found. The user is definitely
+ # NOT a delegate of the sender.
+ log.warning(
+ _("User %s attempted to use envelope sender address %s " + \
+ "without authorization") %(
+ policy_request["sasl_username"],
+ policy_request["sender"]
+ )
+ )
+
+ # Got a final answer here, do the caching thing.
+ if not cache == False:
+ records = cache_select(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
)
- sender_is_delegate = True
+ if not len(records) == len(self.recipients):
+ record_id = cache_insert(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ result=(int)(False),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
- # If nothing matches sender_is_delegate is still None.
- if not sender_is_delegate == True:
sender_is_delegate = False
- return sender_is_delegate
-
-def verify_domain(domain):
- """
- Verify whether the domain is internal (mine) or external.
- """
+ else:
+ # See if we can match the value of the envelope sender delegates to
+ # the actual sender sasl_username
+ if self.sasl_user == None:
+ sasl_user = {
+ 'dn': auth.find_user(
+ # TODO: Use the configured cyrus-sasl result
+ # attribute.
+ search_attrs,
+ self.sasl_username,
+ domain=self.sasl_domain
+ )
+ }
+
+ # Possible values for the kolabDelegate attribute are:
+ # a 'uid', a 'dn'.
+ if not self.sasl_user.has_key('uid'):
+ self.sasl_user['uid'] = auth.get_user_attribute(
+ self.sasl_domain,
+ self.sasl_user,
+ 'uid'
+ )
- domain_verified = False
+ sender_delegates = self.sender_user['kolabdelegate']
- _mydomains = auth.list_domains()
+ if not type(sender_delegates) == list:
+ sender_delegates = [ sender_delegates ]
- for primary, secondaries in _mydomains:
- if primary == domain:
- domain_verified = True
- elif domain in secondaries:
- domain_verified = True
+ for sender_delegate in sender_delegates:
+ if self.sasl_user['dn'] == sender_delegate:
+ log.debug(
+ _("Found user %s to be a delegate user of %s") %(
+ policy_request["sasl_username"],
+ policy_request["sender"]
+ ),
+ level=8
+ )
- if domain_verified == None:
- domain_verified = False
+ sender_is_delegate = True
- return domain_verified
+ elif self.sasl_user['uid'] == sender_delegate:
+ log.debug(
+ _("Found user %s to be a delegate user of %s") %(
+ policy_request["sasl_username"],
+ policy_request["sender"]
+ ),
+ level=8
+ )
-def verify_quota(policy_request):
- """
- Verify the quota usage for this user.
+ sender_is_delegate = True
- Attempt to find a folder annotated with Kolab mail.sentitems
- Attempt to find a folder with \Sent SPECIAL-USE flag
- Use INBOX quota
+ # If nothing matches sender_is_delegate is still None.
+ if not sender_is_delegate == True:
+ sender_is_delegate = False
- If above $x percent, bail out. Get $x from the configuration.
- If spare space below $y, bail out. Get $y from the policy request.
+ return sender_is_delegate
- Typically only used when sending through submission, or when receiving.
- """
+ def verify_recipient(self, recipient):
+ """
+ Verify whether the sender is allowed send to this recipient, using
+ the recipient's kolabAllowSMTPSender.
+ """
- global policy_done
+ self.recipient = recipient
- if policy_request['sasl_username'] == '':
- return True
+ if not self.sasl_username == '' and not self.sasl_username == None:
+ log.debug(_("Verifying authenticated sender '%(sender)s' with " + \
+ "sasl_username '%(sasl_username)s' for recipient " + \
+ "'%(recipient)s'") %(self.__dict__)
+ )
+ else:
+ log.debug(_("Verifying unauthenticated sender '%(sender)s' " + \
+ "for recipient '%(recipient)s'") %(self.__dict__)
+ )
- # TODO: Under some conditions, the recipient may not be fully qualified.
- # We'll cross that bridge when we get there, though.
- domain = policy_request['sasl_username'].split('@')[1]
+ recipient_verified = False
- # Get the quota setting,
- if conf.has_section('kolab_smtp_access_policy'):
- if conf.has_option('kolab_smtp_access_policy', 'max_quota_percentage'):
- max_quota_percentage = conf.get(
- 'kolab_smtp_access_policy',
- 'max_quota_percentage'
+ if not cache == False:
+ records = cache_select(
+ function='verify_recipient',
+ sender=self.sender,
+ recipient=recipient,
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender,
)
+
+ if not records == None and len(records) == 1:
+ log.info(
+ _("Reproducing verify_recipient(%s, %s) from " + \
+ "cache, saving you queries, time and thus " + \
+ "money.") %(self.sender, recipient)
+ )
+
+ return record[0].value
+
+ # TODO: Under some conditions, the recipient may not be fully qualified.
+ # We'll cross that bridge when we get there, though.
+ if len(recipient.split('@')) > 1:
+ sasl_domain = recipient.split('@')[1]
else:
- max_quota_percentage = 101
+ sasl_domain = conf.get('kolab', 'primary_domain')
- else:
- max_quota_percentage = 101
+ if verify_domain(sasl_domain):
+ if auth.secondary_domains.has_key(sasl_domain):
+ log.debug(
+ _("Using authentication domain %s instead of %s") %(
+ auth.secondary_domains[sasl_domain],
+ sasl_domain
+ ),
+ level=8
+ )
+
+ sasl_domain = auth.secondary_domains[sasl_domain]
+ else:
+ log.debug(
+ _("Domain %s is a primary domain") %(
+ sasl_domain
+ ),
+ level=8
+ )
- if verify_domain(domain):
- if auth.secondary_domains.has_key(domain):
- log.debug(_("Using authentication domain %s instead of %s") %(auth.secondary_domains[domain],domain), level=8)
- domain = auth.secondary_domains[domain]
else:
- log.debug(_("Domain %s is a primary domain") %(domain), level=8)
- else:
- log.warning(
- _("Checking the recipient for domain %s that is not " + \
- "ours. This is probably a configuration error.") %(domain)
+ log.warning(
+ _("Checking the recipient for domain %s that is not " + \
+ "ours. This is probably a configuration error.") %(
+ sasl_domain
+ )
+ )
+
+ return True
+
+ search_attrs = conf.get_list(
+ 'kolab_smtp_access_policy',
+ 'address_search_attrs'
)
- return True
+ user = {
+ 'dn': auth.find_user(
+ search_attrs,
+ normalize_address(recipient),
+ domain=sasl_domain,
+ # TODO: Get the filter from the configuration.
+ additional_filter="(&(objectclass=" + \
+ "kolabinetorgperson)%(search_filter)s)"
+ )
+ }
- # Attr search list
- search_attrs = conf.get_list(
- 'kolab_smtp_access_policy',
- 'address_search_attrs'
- )
+ # We have gotten an invalid recipient. We need to catch this case,
+ # because testing can input invalid recipients, and so can faulty
+ # applications, or misconfigured servers.
+ if not user['dn']:
+ if not conf.allow_unauthenticated:
+ cache_update(
+ function='verify_recipient',
+ sender=self.sender,
+ recipient=recipient,
+ result=(int)(False),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
+
+ reject(_("Invalid recipient"))
+ else:
+ cache_update(
+ function='verify_recipient',
+ sender=self.sender,
+ recipient=recipient,
+ result=(int)(True),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
+
+ log.debug(_("Could not find this user, accepting"), level=8)
+ return True
+
+ recipient_policy = auth.get_user_attribute(
+ sasl_domain,
+ user,
+ 'kolabAllowSMTPSender'
+ )
- # Find the user,
- user = {
- 'dn': auth.find_user(
- search_attrs,
- parse_address(policy_request['sasl_username']),
- domain=domain,
- # TODO: Get the filter from the configuration.
- additional_filter="(&(objectclass=kolabinetorgperson)%(search_filter)s)"
+ # If no such attribute has been specified, allow
+ if recipient_policy == None:
+ recipient_verified = True
+
+ # Otherwise, parse the policy obtained with the subject of the policy
+ # being the recipient, and the object to apply the policy to being the
+ # sender.
+ else:
+ recipient_verified = self.parse_policy(
+ recipient,
+ self.sender,
+ recipient_policy
)
- }
- # Find the mailbox,
- mailbox = auth.get_user_attribute(conf.get('cyrus-sasl', 'result_attribute'))
+ cache_update(
+ function='verify_recipient',
+ sender=self.sender,
+ recipient=recipient,
+ result=(int)(recipient_verified),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
- # Get the quota,
- (used,current_quota) = self.imap.lq('user/%s' %(folder))
+ return recipient_verified
- # Compare, and smile or shoot.
- if (current_quota - used) <= policy_request['size']:
- policy_done = True
- reject(_("Not enough storage"))
- return False
+ def verify_recipients(self):
+ """
+ Verify whether the sender is allowed send to the recipients in this
+ policy request, using each recipient's kolabAllowSMTPSender.
- return True
+ Note there may be multiple recipients in this policy request, and
+ therefor self.recipients is a list - walk through that list.
+ """
-def verify_recipient(policy_request):
- """
- Verify whether the sender is allowed send to this recipient, using the
- recipient's kolabAllowSMTPSender.
- """
+ recipients_verified = True
+
+ if not cache == False:
+ records = cache_select(
+ function='verify_recipient',
+ sender=self.sender,
+ recipients=self.recipients,
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender,
+ )
- global policy_done
+ if not records == None and len(records) == len(self.recipients):
+ for record in records:
+ recipient_found = False
+ for recipient in self.recipients:
+ if recipient == record['recipient']:
+ recipient_found = True
- if not policy_request['sasl_username'] == '':
- log.info(_("Verifying authenticated sender '%(sender)s' with " + \
- "sasl_username '%(sasl_username)s' for recipient " + \
- "'%(recipient)s'") %(policy_request)
- )
- else:
- log.info(_("Verifying unauthenticated sender '%(sender)s' " + \
- "for recipient '%(recipient)s'") %(policy_request)
- )
+ if not recipient_found:
+ reject(_("Sender %s is not allowed to send to " + \
+ "recipient %s") %(self.sender,recipient))
- recipient_verified = False
+ for recipient in self.recipients:
+ recipient_verified = self.verify_recipient(recipient)
+ if not recipient_verified:
+ recipients_verified = False
- if not cache == False:
- record = cache_select(
- sender=policy_request['sender'],
- recipient=policy_request['recipient'],
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender'],
- function='verify_recipient'
- )
+ return recipients_verified
- if not record == None:
- log.info(_("Reproducing verify_recipient(%r) from cache, " + \
- "saving you queries, time and thus money.") %(
- policy_request
- )
+ def verify_sender(self):
+ """
+ Verify the sender's access policy.
+
+ 1) Verify whether the sasl_username is allowed to send using the
+ envelope sender address, with the kolabDelegate attribute
+ associated with the LDAP object that has the envelope sender
+ address.
+
+ 2) Verify whether the sender is allowed to send to recipient(s)
+ listed on the sender's object.
+
+ A third potential action could be to check the recipient object to
+ see if the sender is allowed to send to the recipient by the
+ recipient's kolabAllowSMTPSender, but this is done in
+ verify_recipients().
+ """
+
+ sender_verified = False
+
+ if not cache == False:
+ records = cache_select(
+ sender=self.sender,
+ recipients=self.recipients,
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender,
+ function='verify_sender'
)
- return record.value
+ if not records == None and len(records) == len(self.recipients):
+ log.info(_("Reproducing verify_sender(%r) from cache, " + \
+ "saving you queries, time and thus money.") %(
+ self.__dict__
+ )
+ )
- # TODO: Under some conditions, the recipient may not be fully qualified.
- # We'll cross that bridge when we get there, though.
- domain = policy_request['recipient'].split('@')[1]
+ for record in records:
+ recipient_found = False
+ for recipient in self.recipients:
+ if recipient == record.recipient:
+ recipient_found = True
+
+ if recipient_found and not record.value:
+ reject(_("Sender %s is not allowed to send to " + \
+ "recipient %s") %(self.sender,recipient))
+
+ return True
+
+ self.verify_authenticity()
+ self.sasl_user_uses_alias = self.verify_alias()
+
+ if not self.sasl_user_uses_alias:
+ self.sasl_user_is_delegate = self.verify_delegate()
+
+ # If the authenticated user is using delegate functionality, apply the
+ # recipient policy attribute for the envelope sender.
+ if self.sasl_user_is_delegate == False and \
+ self.sasl_user_uses_alias == False:
+
+ reject(_("Sender uses unauthorized envelope sender address"))
+
+ elif self.sasl_user_is_delegate:
+ # Apply the recipient policy for the sender using the envelope
+ # sender user object.
+ recipient_policy_domain = self.sender_domain
+ recipient_policy_sender = self.sender
+ recipient_policy_user = self.sender_user
+
+ elif not self.sasl_user == None:
+ # Apply the recipient policy from the authenticated user.
+ recipient_policy_domain = self.sasl_domain
+ recipient_policy_sender = self.sasl_username
+ recipient_policy_user = self.sasl_user
- if verify_domain(domain):
- if auth.secondary_domains.has_key(domain):
- log.debug(_("Using authentication domain %s instead of %s") %(auth.secondary_domains[domain],domain), level=8)
- domain = auth.secondary_domains[domain]
else:
- log.debug(_("Domain %s is a primary domain") %(domain), level=8)
- else:
- log.warning(
- _("Checking the recipient for domain %s that is not " + \
- "ours. This is probably a configuration error.") %(domain)
+ if not conf.allow_unauthenticated:
+ reject(_("Could not verify sender"))
+ else:
+ recipient_policy_domain = self.sender_domain
+ recipient_policy_sender = self.sender
+ recipient_policy_user = self.sender_user
+
+ log.debug(
+ _("Verifying whether sender is allowed to send to " + \
+ "recipient using sender policy"),
+ level=8
)
- return True
+ if recipient_policy_user.has_key('kolaballowsmtprecipient'):
+ recipient_policy = recipient_policy_user['kolaballowsmtprecipient']
+ else:
+ recipient_policy = auth.get_user_attribute(
+ recipient_policy_domain,
+ recipient_policy_user,
+ 'kolabAllowSMTPRecipient'
+ )
- search_attrs = conf.get_list(
- 'kolab_smtp_access_policy',
- 'address_search_attrs'
- )
+ log.debug(_("Result is %r") %(recipient_policy), level=8)
- user = {
- 'dn': auth.find_user(
- search_attrs,
- parse_address(policy_request['recipient']),
- domain=domain,
- # TODO: Get the filter from the configuration.
- additional_filter="(&(objectclass=kolabinetorgperson)%(search_filter)s)"
+ # If no such attribute has been specified, allow
+ if recipient_policy == None:
+ log.debug(
+ _("No recipient policy restrictions exist for this sender"),
+ level=8
)
- }
- # We have gotten an invalid recipient. We need to catch this case, because
- # testing can input invalid recipients, and so can faulty applications, or
- # misconfigured servers.
- if not user['dn']:
- if not conf.allow_unauthenticated:
- reject(_("Invalid recipient"))
- policy_done = True
- return False
+ sender_verified = True
+
+ # Otherwise,parse the policy obtained.
else:
- log.debug(_("Could not find this user, accepting"), level=8)
- return True
+ log.debug(
+ _("Found a recipient policy to apply for this sender."),
+ level=8
+ )
- recipient_policy = auth.get_user_attribute(
- domain,
- user,
- 'kolabAllowSMTPSender'
- )
+ recipient_allowed = None
- # If no such attribute has been specified, allow
- if recipient_policy == None:
- recipient_verified = True
+ for recipient in self.recipients:
+ recipient_allowed = self.parse_policy(
+ recipient_policy_sender,
+ recipient,
+ recipient_policy
+ )
- # Otherwise, match the values in allowed_senders to the actual sender
- else:
- recipient_verified = parse_policy(
- policy_request['sasl_username'],
- policy_request['recipient'],
- recipient_policy
- )
+ if not recipient_allowed:
+ reject(
+ _("Sender %s not allowed to send to recipient " + \
+ "%s") %(recipient_policy_user['dn'],recipient)
+ )
- if not cache == False:
- result = cache_select(
- sender=policy_request['sender'],
- recipient=policy_request['recipient'],
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender'],
- function='verify_recipient'
- )
+ sender_verified = True
- if result == None:
- record_id = cache_insert(
- policy_request['sender'],
- policy_request['recipient'],
- 'verify_recipient',
- (int)(recipient_verified),
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender']
+ if not cache == False:
+ records = cache_select(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender,
)
- return recipient_verified
+ if len(records) == len(self.recipients):
+ record_id = cache_insert(
+ function='verify_sender',
+ sender=self.sender,
+ recipients=self.recipients,
+ result=(int)(sender_verified),
+ sasl_username=self.sasl_username,
+ sasl_sender=self.sasl_sender
+ )
-def verify_sender(policy_request):
- """
- Verify the sender's access policy.
+ return sender_verified
- 1) Verify whether the sasl_username is allowed to send using the
- envelope sender address, with the kolabDelegate attribute associated
- with the LDAP object that has the envelope sender address.
+def cache_cleanup():
+ if not cache == True:
+ return
- 2) Verify whether the sender is allowed to send to recipient(s) listed
- on the sender's object.
+ log.debug(_("Cleaning up the cache"), level=8)
+ session.query(
+ PolicyResult
+ ).filter(
+ PolicyResult.created < ((int)(time.time()) - cache_expire)
+ ).delete()
- A third potential action could be to check the recipient object to see
- if the sender is allowed to send to the recipient by the recipient's
- kolabAllowSMTPSender, but this is done in verify_recipient().
- """
+def cache_init():
+ global cache, cache_expire, session
- if not policy_request['sasl_username'] == '':
- log.info(_("Verifying authenticated sender '%(sender)s' with " + \
- "sasl_username '%(sasl_username)s'") %(policy_request)
- )
+ if conf.has_section('kolab_smtp_access_policy'):
+ if conf.has_option('kolab_smtp_access_policy', 'uri'):
+ cache_uri = conf.get('kolab_smtp_access_policy', 'uri')
+ cache = True
+ if conf.has_option('kolab_smtp_access_policy', 'retention'):
+ cache_expire = (int)(
+ conf.get(
+ 'kolab_smtp_access_policy',
+ 'retention'
+ )
+ )
+
+ else:
+ return False
else:
- log.info(_("Verifying unauthenticated sender '%(sender)s'") %(
- policy_request
- )
- )
+ return False
- sender_verified = False
+ if conf.debuglevel > 8:
+ engine = create_engine(cache_uri, echo=True)
+ else:
+ engine = create_engine(cache_uri, echo=False)
- sender_is_delegate = None
- sender_uses_alias = None
+ try:
+ metadata.create_all(engine)
+ except sqlalchemy.exc.OperationalError, e:
+ log.error(_("Operational Error in caching: %s" %(e)))
+ return False
- sasl_user = False
+ Session = sessionmaker(bind=engine)
+ session = Session()
- if not cache == False:
- record = cache_select(
- sender=policy_request['sender'],
- recipient=policy_request['recipient'],
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender'],
- function='verify_sender'
- )
+ return cache
- if not record == None:
- log.info(_("Reproducing verify_sender(%r) from cache, " + \
- "saving you queries, time and thus money.") %(
- policy_request
- )
- )
+def cache_select(
+ function,
+ sender,
+ recipient='',
+ recipients=[],
+ sasl_username='',
+ sasl_sender=''
+ ):
- return record
+ if not cache == True:
+ return None
- sender_domain = policy_request['sender'].split('@')[1]
+ if not recipient == '':
+ recipients.append(recipient)
+
+ return session.query(
+ PolicyResult
+ ).filter_by(
+ key=function,
+ sender=sender,
+ sasl_username=sasl_username,
+ sasl_sender=sasl_sender
+ ).filter(
+ PolicyResult.recipient.in_(recipients)
+ ).filter(
+ PolicyResult.created >= ((int)(time.time()) - cache_expire)
+ ).all()
+
+def cache_insert(
+ function,
+ sender,
+ recipient='',
+ recipients=[],
+ result=None,
+ sasl_username='',
+ sasl_sender=''
+ ):
+
+ if not cache == True:
+ return []
- # Obtain 'kolabDelegate' from the envelope sender.
log.debug(
- _("Obtaining envelope sender dn for %s") %(
- policy_request['sender']
+ _("Caching the policy result with timestamp %d") %(
+ (int)(time.time())
),
level=8
)
- search_attrs = conf.get_list(
- 'kolab_smtp_access_policy',
- 'address_search_attrs'
+ cache_cleanup()
+
+ if not recipient == '':
+ recipients.append(recipient)
+
+ for recipient in recipients:
+ session.add(
+ PolicyResult(
+ key=function,
+ value=result,
+ sender=sender,
+ recipient=recipient,
+ sasl_username=sasl_username,
+ sasl_sender=sasl_sender
+ )
+ )
+
+ session.commit()
+
+def cache_update(
+ function,
+ sender,
+ recipient='',
+ recipients=[],
+ result=None,
+ sasl_username='',
+ sasl_sender=''
+ ):
+
+ """
+ Insert an updated set of rows into the cache depending on the necessity
+ """
+
+ if not cache == True:
+ return
+
+ records = []
+
+ _records = cache_select(
+ function,
+ sender,
+ recipient,
+ recipients,
+ sasl_username,
+ sasl_sender
)
- sender_user = {
- 'dn': auth.find_user(
- search_attrs,
- parse_address(policy_request['sender']),
- domain=sender_domain
+ for record in _records:
+ if record.value == (int)(result):
+ records.append(record)
+
+ if not recipient == '':
+ recipients.append(recipient)
+ recipient = ''
+
+ for recipient in recipients:
+ recipient_found = False
+ for record in records:
+ if record.recipient == recipient:
+ recipient_found = True
+ if not recipient_found:
+ cache_insert(
+ function=function,
+ sender=sender,
+ recipient=recipient,
+ result=result,
+ sasl_username=sasl_username,
+ sasl_sender=sasl_sender
)
- }
- if not sender_user['dn']:
- if conf.allow_unauthenticated:
- return True
- else:
- policy_done = True
- reject(_("Not allowing unauthenticated users, but sender not found"))
- return False
+def defer_if_permit(message, policy_request=None):
+ log.info(_("Returning action DEFER_IF_PERMIT: %s") %(message))
+ print "action=DEFER_IF_PERMIT %s\n\n" %(message)
+ sys.exit(0)
- log.debug(_("Found user object %(dn)s") %(sender_user), level=8)
+def dunno(message, policy_request=None):
+ log.info(_("Returning action DUNNO: %s") %(message))
+ print "action=DUNNO %s\n\n" %(message)
+ sys.exit(0)
- # Only when a user is authenticated do we have the means to check for
- # kolabDelegate functionality.
- if not policy_request['sasl_username'] == '':
- sender_is_delegate = verify_delegate(
- policy_request,
- sender_domain,
- sender_user
- )
+def hold(message, policy_request=None):
+ log.info(_("Returning action HOLD: %s") %(message))
+ print "action=HOLD %s\n\n" %(message)
+ sys.exit(0)
- sender_uses_alias = verify_alias(
- policy_request,
- sender_domain,
- sender_user
- )
+def permit(message, policy_request=None):
+ log.info(_("Returning action PERMIT: %s") %(message))
+ print "action=PERMIT\n\n"
+ sys.exit(0)
- # If the authenticated user is using delegate functionality, apply the
- # recipient policy attribute for the envelope sender.
- if sender_is_delegate == False and sender_uses_alias == False:
- return False
+def reject(message, policy_request=None):
+ log.info(_("Returning action REJECT: %s") %(message))
+ print "action=REJECT %s\n\n" %(message)
+ sys.exit(0)
- elif sender_is_delegate:
- recipient_policy_domain = sender_domain
- recipient_policy_sender = parse_address(policy_request['sender'])
- recipient_policy_user = sender_user
- elif not policy_request['sasl_username'] == '':
- sasl_domain = policy_request['sasl_username'].split('@')[1]
- recipient_policy_domain = sasl_domain
- recipient_policy_sender = policy_request['sasl_username']
- if not sasl_user:
- sasl_user = {
- 'dn': auth.find_user(
- # TODO: Use the configured cyrus-sasl result
- # attribute
- 'mail',
- parse_address(policy_request['sasl_username']),
- domain=sasl_domain
- )
- }
+def expand_mydomains():
+ """
+ Return a list of my domains.
+ """
- recipient_policy_user = sasl_user
- else:
- if not conf.allow_unauthenticated:
- reject(_("Could not verify sender"))
- else:
- recipient_policy_domain = sender_domain
- recipient_policy_sender = parse_address(policy_request['sender'])
- recipient_policy_user = sender_user
+ mydomains = []
- log.debug(_("Verifying whether sender is allowed to send to recipient " + \
- "using sender policy"), level=8)
+ _mydomains = auth.list_domains()
- recipient_policy = auth.get_user_attribute(
- recipient_policy_domain,
- recipient_policy_user,
- 'kolabAllowSMTPRecipient'
- )
+ for primary, secondaries in _mydomains:
+ mydomains.append(primary)
+ for secondary in secondaries:
+ mydomains.append(secondary)
- log.debug(_("Result is '%r'") %(recipient_policy), level=8)
+ return mydomains
- # If no such attribute has been specified, allow
- if recipient_policy == None:
- log.debug(
- _("No recipient policy restrictions exist for this sender"),
- level=8
+def normalize_address(email_address):
+ """
+ Parse an address; Strip off anything after a recipient delimiter.
+ """
+
+ # TODO: Recipient delimiter is configurable.
+ if len(email_address.split("+")) > 1:
+ # Take the first part split by recipient delimiter and the last part
+ # split by '@'.
+ return "%s@%s" %(
+ email_address.split("+")[0],
+ # TODO: Under some conditions, the recipient may not be fully
+ # qualified. We'll cross that bridge when we get there, though.
+ email_address.split('@')[1]
)
+ else:
+ return email_address
- sender_verified = True
+def read_request_input():
+ """
+ Read a single policy request from sys.stdin, and return a dictionary
+ containing the request.
+ """
- # Otherwise, match the values in allowed_recipients to the actual recipients
- else:
- log.debug(
- _("Found a recipient policy to apply for this sender."),
- level=8
- )
+ policy_request = {}
- sender_verified = parse_policy(
- recipient_policy_sender,
- policy_request['recipient'],
- recipient_policy
- )
+ end_of_request = False
+ while not end_of_request:
+ request_line = sys.stdin.readline()
+ if request_line.strip() == '':
+ if policy_request.has_key('request'):
+ end_of_request = True
+ else:
+ request_line = request_line.strip()
+ policy_request[request_line.split('=')[0]] = \
+ '='.join(request_line.split('=')[1:]).lower()
- log.debug(
- _("Recipient policy evaluated to '%r'.") %(sender_verified),
- level=8
- )
+ return policy_request
- if not cache == False:
- result = cache_select(
- sender=policy_request['sender'],
- recipient=policy_request['recipient'],
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender'],
- function='verify_sender'
- )
+def verify_domain(domain):
+ """
+ Verify whether the domain is internal (mine) or external.
+ """
- if result == None:
- record_id = cache_insert(
- policy_request['sender'],
- policy_request['recipient'],
- 'verify_sender',
- (int)(sender_verified),
- sasl_username=policy_request['sasl_username'],
- sasl_sender=policy_request['sasl_sender']
- )
+ domain_verified = False
- return sender_verified
+ _mydomains = auth.list_domains()
+
+ for primary, secondaries in _mydomains:
+ if primary == domain:
+ domain_verified = True
+ elif domain in secondaries:
+ domain_verified = True
+
+ if domain_verified == None:
+ domain_verified = False
+
+ return domain_verified
if __name__ == "__main__":
access_policy_group = conf.add_cli_parser_option_group(
@@ -955,136 +1218,39 @@ if __name__ == "__main__":
cache = cache_init()
+ policy_requests = {}
+
# Start the work
while True:
policy_request = read_request_input()
- break
-
- # Set the overall default policy in case nothing attracts any particular
- # type of action.
- #
- # When either is configured or specified to be verified, negate
- # that policy to be false by default.
- #
- policy_done = False
- sender_allowed = True
- recipient_allowed = True
-
- if conf.has_option('kolab_smtp_access_policy', 'allow_helo_names'):
- if policy_request['helo_name'] in conf.get('kolab_smtp_access_policy', 'allow_helo_names'):
- permit(_("Trusted HELO sender"))
- policy_done = True
-
- if not policy_done:
- if conf.verify_sender:
- sender_allowed = False
-
- log.debug(_("Verifying sender."), level=8)
-
- # If no sender is specified, we bail out.
- if policy_request['sender'] == "":
- log.debug(_("No sender specified."), level=8)
- reject(_("Invalid sender"))
- policy_done = True
-
- # If no sasl username exists, ...
- if policy_request['sasl_username'] == "":
- log.debug(_("No SASL username in request."), level=8)
-
- if not conf.allow_unauthenticated:
- log.debug(
- _("Not allowing unauthenticated senders."),
- level=8
- )
-
- reject(_("Access denied for unauthenticated senders"))
- policy_done = True
-
- else:
- log.debug(_("Allowing unauthenticated senders."), level=8)
-
- if not verify_domain(policy_request['sender'].split('@')[1]):
- sender_allowed = True
- permit(_("External sender"))
- policy_done = True
-
- else:
- sender_allowed = verify_sender(policy_request)
-
- # If the authenticated username is the sender...
- elif policy_request["sasl_username"] == policy_request["sender"]:
- log.debug(
- _("Allowing authenticated sender %s to send as %s.") %(
- policy_request["sasl_username"],
- policy_request["sender"]
- ),
- level=8
- )
-
- sender_allowed = True
-
- permit(
- _("Authenticated as sender %s") %(
- policy_request['sender']
- )
- )
-
- policy_done = True
-
- # Or if the authenticated username is the sender but the sender address
- # lists an address with a recipient delimiter...
- #
- # TODO: The recipient delimiter is configurable!
- elif policy_request["sasl_username"] == \
- parse_address(
- policy_request["sender"]
- ):
-
- sender_allowed = True
-
- permit(
- _("Authenticated as sender %s") %(
- parse_address(policy_request["sender"])
- )
- )
-
- policy_done = True
-
- else:
- sender_allowed = verify_sender(policy_request)
-
- if conf.verify_recipient:
- recipient_allowed = False
-
- log.debug(_("Verifying recipient."), level=8)
-
- if policy_request['recipient'] == "":
- reject(_("Invalid recipient"))
- policy_done = True
+ instance = policy_request['instance']
+ if policy_requests.has_key(instance):
+ policy_requests[instance].add_request(policy_request)
+ else:
+ policy_requests[instance] = PolicyRequest(policy_request)
- if policy_request['sasl_username'] == "":
- log.debug(_("No SASL username in request."), level=8)
+ # We can recognize being in the DATA part by the recipient_count being
+ # set to a non-zero value. Note that the input we're getting is a
+ # string, not an integer.
- if not conf.allow_unauthenticated:
- log.debug(_("Not allowing unauthenticated senders."), level=8)
- reject(_("Access denied for unauthenticated senders"))
- policy_done = True
+ if policy_request.has_key('recipient_count'):
+ if (int)(policy_request['recipient_count']) > 0:
+ sender_allowed = False
+ recipient_allowed = False
+ if conf.verify_sender:
+ sender_allowed = policy_requests[instance].verify_sender()
else:
- recipient_allowed = verify_recipient(policy_request)
- else:
- recipient_allowed = verify_recipient(policy_request)
-
- if not policy_done:
- # TODO: Insert whitelists.
- if conf.verify_sender and not sender_allowed:
- reject(_("Sender access denied"), policy_request)
-
- elif conf.verify_recipient and not recipient_allowed:
- reject(_("Recipient access denied"), policy_request)
+ sender_allowed = True
- else:
- permit(_("No objections"))
+ if conf.verify_recipient:
+ recipient_allowed = policy_requests[instance].verify_recipients()
+ else:
+ recipient_allowed = True
- if cache:
- cache_cleanup()
+ if not sender_allowed:
+ reject(_("Sender access denied"))
+ elif not recipient_allowed:
+ reject(_("Recipient access denied"))
+ else:
+ permit(_("No objections"))