steffen: server/kolabd/kolabd kolab_smtpdpolicy.in,1.5,1.6

cvs at intevation.de cvs at intevation.de
Mon Nov 28 05:16:03 CET 2005


Author: steffen

Update of /kolabrepository/server/kolabd/kolabd
In directory doto:/tmp/cvs-serv32156/kolabd

Modified Files:
	kolab_smtpdpolicy.in 
Log Message:
Cleaned up smtpd policy server. Besides the section with the "fishy" comment I am quite confident the other checks dont pose any risks of being incompatible with internet email

Index: kolab_smtpdpolicy.in
===================================================================
RCS file: /kolabrepository/server/kolabd/kolabd/kolab_smtpdpolicy.in,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- kolab_smtpdpolicy.in	27 Nov 2005 23:57:02 -0000	1.5
+++ kolab_smtpdpolicy.in	28 Nov 2005 04:16:01 -0000	1.6
@@ -1,4 +1,4 @@
-#!@PERL@
+#!@PERL@ -w
 
 ##
 ##  Copyright (c) 2004  Klaraelvdalens Datakonsult AB
@@ -19,6 +19,7 @@
 ##  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 ##
 
+use strict;
 use Fcntl;
 use Sys::Syslog qw(:DEFAULT setlogsock);
 use URI;
@@ -34,7 +35,7 @@
 # kolabdelegated Postfix SMTPD policy server for Kolab. This server implements
 # various policies for Kolab:
 #
-# 1) Only authenticated users can use From addresses <username>@$domain
+# 1) Only authenticated users can use sender <username>@$domain
 # 2) Some distribution lists are only available to authenticated users
 #
 # Logging is sent to syslogd.
@@ -64,7 +65,7 @@
 #
 # To test this script by hand, execute:
 #
-#    % perl kolab_smtpd_policy.pl
+#    % perl kolab_smtpd_policy
 #
 # Each query is a bunch of attributes. Order does not matter, and
 # the demo script uses only a few of all the attributes shown below:
@@ -98,6 +99,9 @@
 # work on your system.
 #
 my %conf;
+my %attr;
+my $ldap;
+my $verbose;
 my $syslog_socktype = 'unix'; # inet, unix, stream, console
 my $syslog_facility="mail";
 my $syslog_options="pid";
@@ -105,6 +109,20 @@
 
 my $ldap_max_tries = 5;
 
+#
+# Read options from config-file
+#
+my $conf_allowunauth = 0;
+%conf = readConfig( %conf, "@sysconfdir@/kolab/kolab_smtpdpolicy.conf" );
+my $conf_ldapuri = $conf{'ldap_uri'};
+my $conf_basedn  = $conf{'basedn'};
+my $conf_binddn   = $conf{'binddn'};
+my $conf_bindpw  = $conf{'bindpw'};
+my @conf_domain  = $conf{'domain'};
+$conf_allowunauth = 1 if( $conf{'allow_unauth'} );
+my @conf_permithosts = split /\s*,\s*/, $conf{'permithosts'};
+
+
 sub mylog {
   my $prio = shift;
   my $fmt = shift;
@@ -113,6 +131,7 @@
 
   #Kolab::log( 'P', $text );
   syslog $prio, $text;
+  print "$text\n";
 }
 
 sub contains {
@@ -137,132 +156,151 @@
     }
 }
 
-#
-# SMTPD access policy routine. The result is an action just like
-# it would be specified on the right-hand side of a Postfix access
-# table.  Request attributes are available via the %attr hash.
-#
-sub smtpd_access_policy {
-
-  # Get relevant attributes
-  my $sender      = $attr{'sender'};
-  my $recip       = $attr{'recipient'};
-  my $username    = $attr{'sasl_username'};
-  my $client_addr = $attr{'client_address'};
-
-  mylog($syslog_priority, "Checking sender=\"$sender\", recipient=\"$recip\", username=\"$username\", domains=".join(',', at conf_domain)." permithosts=".join(',', at conf_permithosts).", conf_allowunauth=$conf_allowunauth") if $verbose;
-
-  #### This should probably be simplifed to
-  #### reject sender <anything>@domain.tld if the user is
-  #### not authenticated
-
- CHECKPERMITHOSTS:
-  # First check if the sender is a privileged kolabhost
-  # Kolab hosts use un-authenticated smtp currently
-  if( !$username ) {
-      for my $host (@conf_permithosts) {
-	  unless ($h = gethost($host)) {
-	      mylog($syslog_priority,"No such host $host\n");
-	      next;
-	  }
-	  for my $addr ( @{$h->addr_list} ) {
-	      return "DUNNO" if inet_ntoa($addr) eq $client_addr;
-	  }
-      }
-  }
-
-  # Translate uid to mail:
+sub lookup_uid {
   my $tries = 0;
- LOOKUPUID:
+  my $uid = shift;
+ AGAIN:
   my $mesg = $ldap->search( base=> $conf_basedn,
 			    scope=> 'sub',
-			    filter=> "(&(objectClass=kolabinetorgperson)(|(mail=$username)(uid=$username)))");
+			    filter=> "(&(objectClass=kolabinetorgperson)(|(mail=$uid)(uid=$uid)))",
+			    attrs => [ 'uid'] );
   if( !$mesg->code && $mesg->count() > 0 ) {
       mylog($syslog_priority, "LDAP search returned ".$mesg->count()." objects") if $verbose;
       my $ldapobject = $mesg->entry(0);
-      $username = lc($ldapobject->get_value('mail'));
-      mylog($syslog_priority, "Translated username to $username") if $verbose;
+      $uid = lc($ldapobject->get_value('uid'));
+      mylog($syslog_priority, "Translated username to $uid") if $verbose;
   } elsif( $mesg->code && $mesg->code != LDAP_NO_SUCH_OBJECT ) {
       if( $tries++ <= $ldap_max_tries ) {
 	  ldap_connect;
-	  goto LOOKUPUID;
+	  goto AGAIN;
       } else {
 	  mylog($syslog_priority, "LDAP Connection error during LOOKUPUID: ".
 		$mesg->error." after $ldap_max_tries attempts to reconnect. Giving up!" );
-	  return "DEFER_IF_PERMIT LDAP Error during LOOKUPUID:: ".$mesg->error;
+	  die( "LDAP Error looking up uid: ".$mesg->error );
       }
   }
-  # Check for allowed sender
-  $tries = 0;
- CHECKSENDER:
+  return $uid;
+}
 
-  $mesg = $ldap->search( base=> $conf_basedn,
+sub check_permithosts {
+  my $client_addr = shift;
+  for my $host (@conf_permithosts) {
+    my $h;
+    unless ($h = gethost($host)) {
+      mylog($syslog_priority,"No such host $host\n");
+      next;
+    }
+    for my $addr ( @{$h->addr_list} ) {
+      return 1 if inet_ntoa($addr) eq $client_addr;
+    }
+  }
+  return undef;
+}
+
+sub lookup_sender_uids {
+  my $sender = shift;
+  my $tries = 0;
+  my @result;
+ AGAIN:
+  my $mesg = $ldap->search( base=> $conf_basedn,
 			    scope=> 'sub',
-			    filter=> "(&(objectClass=kolabinetorgperson)(|(mail=$sender)(alias=$sender)))");
-  if( !$mesg->code ) {
+			    filter=> "(&(objectClass=kolabinetorgperson)(|(mail=$sender)(alias=$sender)))",
+			    attrs => [ 'uid' ]);
+  if( !$mesg->code && $mesg->count() > 0 ) {
     mylog($syslog_priority, "LDAP search returned ".$mesg->count()." objects") if $verbose;
-    foreach $ldapobject ($mesg->entries) {
-      mylog($syslog_priority, "Got object ".$ldapobject->get_value('uid') ) if $verbose;
-      mylog($syslog_priority, "Got delegates ".join(", ", @{$ldapobject->get_value('kolabdelegate', asref => 1 )})) if $verbose;
-      if( $username && ( lc($username) eq lc($ldapobject->get_value('uid'))  ||
-                         lc($username) eq lc($ldapobject->get_value('mail')) ||
-	                 contains( $username, $ldapobject->get_value('kolabdelegate', asref => 1 )))  ) {
-	# All OK, user is sending as herself or as kolabdelegate
-	mylog($syslog_priority, "$username using valid from address $sender") if $verbose;	
-	goto CHECKDISTLIST;
-      }
+    foreach my $entry ( $mesg->entries ) {
+      mylog($syslog_priority, lc($entry->get_value('uid')." is allowed to use ".$sender)) if $verbose;
+      push @result, lc($entry->get_value('uid'));
+    }
+  } elsif( $mesg->code && $mesg->code != LDAP_NO_SUCH_OBJECT ) {
+    if( $tries++ <= $ldap_max_tries ) {
+      ldap_connect;
+      goto AGAIN;
+    } else {
+      die( "LDAP Error looking up uid for sender £sender: ".$mesg->error );
     }
-  } else {
-      # LDAP error?
-      if( $mesg->code != LDAP_NO_SUCH_OBJECT && $tries++ <= $ldap_max_tries ) {
-	  mylog($syslog_priority, "LDAP Connection error during CHECKSENDER: ".$mesg->error.", trying to reconnect" );
-	  ldap_connect;
-	  goto CHECKSENDER;
-      } else {
-	  mylog($syslog_priority, "Query returned error during CHECKSENDER: ".$mesg->error ) if $verbose;	
-	  return "DEFER_IF_PERMIT Temporary LDAP error during CHECKSENDER: ".$mesg->error;
-      }
   }
-  if( $conf_allowunauth && !$username ) {
-    # Dont reject mail from other domains
-    $sender =~ /(.*)@(.*)/;
-    my $domain = $2;
-    mylog($syslog_priority, "sender=$sender, domain=$domain") if $verbose;
-    if( !contains( $domain, \@conf_domain ) ) {
-      # Ok
-      mylog($syslog_priority, "sending from other domains OK") if $verbose;
-      goto CHECKDISTLIST;
+  return @result;
+};
+
+sub check_dist_list {
+  my $username = shift;
+  my $recipient = shift;
+  my $tries = 0;
+ AGAIN:
+  if( !$username ) {
+    my $mesg = $ldap->search( base=> "cn=internal,".$conf_basedn,
+			   scope=> 'one', filter=> "(&(mail=$recipient)(objectClass=kolabgroupofnames))");
+    if( !$mesg->code && $mesg->count() > 0 ) {
+      # Ups, recipient is a restricted list, reject
+      mylog( $syslog_priority, "Attempt from $username to access restricted list $recipient" ) if $verbose;	
+      return undef;
+    } elsif( $mesg->code && $mesg->code != LDAP_NO_SUCH_OBJECT && $tries++ <= $ldap_max_tries ) {
+      mylog($syslog_priority, "LDAP Connection error during CHECKDISTLIST: ".$mesg->error.", trying to reconnect" );
+      ldap_connect;
+      goto AGAIN;
+    } elsif( $mesg->code ) {
+      mylog( $syslog_priority, "LDAP Error during CHECKDISTLIST: ".$mesg->error ) if $verbose;
+      # Just fall through and accept the message in case there was an LDAP problem.
     }
   }
-  # UPS, fake sender
-  mylog($syslog_priority, "Attempt to fake address $sender") if $verbose;	
-  return "REJECT Invalid sender";
+  return 1;
+}
 
-  # Check for valid access to restricted distribution lists
-  $tries = 0;
- CHECKDISTLIST:
-  if( !$username or $username eq '' ) {
-    $recip =~ /(.*)@(.*)/;
-    my $cn = $1;
-    my $domain = $2;
-    if( contains($domain,\@conf_domain ) ) {
-      $mesg = $ldap->search( base=> "cn=internal,".$conf_basedn,
-                             scope=> 'one', filter=> "(&(cn=$cn)(objectClass=kolabgroupofnames))");
-      if( !$mesg->code && $mesg->count() > 0 ) {
-	# Ups, recipient is a restricted list, reject
-	mylog( $syslog_priority, "Attempt from $sender to access restricted list $recip" ) if $verbose;	
-	return "REJECT Access denied";
-      } elsif( $mesg->code && $mesg->code != LDAP_NO_SUCH_OBJECT && $tries++ <= $ldap_max_tries ) {
-	  mylog($syslog_priority, "LDAP Connection error during CHECKDISTLIST: ".$mesg->error.", trying to reconnect" );
-	  ldap_connect;
-	  goto CHECKDISTLIST;
-      } elsif( $mesg->code ) {
-	mylog( $syslog_priority, "LDAP Error during CHECKDISTLIST: ".$mesg->error ) if $verbose;
-	# Just fall through and accept the message in case there was an LDAP problem.
-      }
+#
+# SMTPD access policy routine. The result is an action just like
+# it would be specified on the right-hand side of a Postfix access
+# table.  Request attributes are available via the %attr hash.
+#
+sub smtpd_access_policy {
+
+  # Get relevant attributes
+  my $sender      = lc($attr{'sender'});
+  my $recipient   = lc($attr{'recipient'});
+  my $username    = lc($attr{'sasl_username'});
+  my $client_addr = lc($attr{'client_address'});
+
+  mylog($syslog_priority, "Checking sender=\"$sender\", recipient=\"$recipient\", username=\"$username\", domains=".join(',', at conf_domain)." permithosts=".join(',', at conf_permithosts).", conf_allowunauth=$conf_allowunauth") if $verbose;
+
+  # First check if the sender is a privileged kolabhost
+  # Kolab hosts use un-authenticated smtp currently
+  return "DUNNO" if( !$username && check_permithosts($client_addr) );
+
+  # Reject anything else from unauthenticated users
+  # if conf_allowunauth is false
+  return "REJECT Acces denied" if( !$username && !$conf_allowunauth );
+
+  eval{ $username = lookup_uid($username) }; return "DEFER_IF_PERMIT $@" if $@;
+
+  # See if sender is owned by someone
+  my @uids;
+  eval { @uids = lookup_sender_uids($sender) }; return "DEFER_IF_PERMIT $@" if $@;
+  if( scalar(@uids) > 0 ) {
+    if( contains( $username, \@uids ) ) {
+      mylog($syslog_priority, "$username using $sender is OK, accepting") if $verbose;
+      return "DUNNO";
+    } else {
+      mylog($syslog_priority, "$username trying to use $sender is NOT OK, rejecting") if $verbose;
+      return "REJECT Invalid sender";
+    }
+  } else {
+    # OK, here things get fishy! The above check
+    # ensures that nobody is using someone else's
+    # email address. That is perfectly valid, but
+    # people want tighter restrictions and disallow
+    # use of _any_ (real or imagined) email address
+    # that the user is not explicitly allowed to use.
+    # Do _have_ to allow the empty sender though,
+    # otherwise hell breaks loose...
+    if( $username ne '' && $sender ne '' ) {
+      mylog($syslog_priority, "$username trying to use $sender is NOT OK, rejecting") if $verbose;
+      return "REJECT Invalid sender";
     }
   }
 
+  # Check for valid access to restricted distribution lists
+  return "REJECT Access denied" unless check_dist_list($username, $recipient);
+
   # The result can be any action that is allowed in a Postfix access(5) map.
   #
   # To label mail, return ``PREPEND'' headername: headertext
@@ -273,7 +311,7 @@
   # In case of failure, specify ``DEFER_IF_PERMIT optional text...''
   # so that mail can still be blocked by other access restrictions.
 
-  mylog($syslog_priority, "sender $sender, recipient $recip seems ok") if $verbose;
+  mylog($syslog_priority, "sender $sender, recipient $recipient seems ok") if $verbose;
 
   return "DUNNO";
 }
@@ -305,22 +343,9 @@
 openlog $0, $syslog_options, $syslog_facility;
 
 #
-# Read options from config-file
-#
-$conf_allowunauth = 0;
-%conf = readConfig( %conf, "@sysconfdir@/kolab/kolab_smtpdpolicy.conf" );
-$conf_ldapuri = $conf{'ldap_uri'};
-$conf_basedn  = $conf{'basedn'};
-$conf_bindn   = $conf{'binddn'};
-$conf_bindpw  = $conf{'bindpw'};
-$conf_domain  = $conf{'domain'};
-$conf_allowunauth = 1 if( $conf{'allow_unauth'} );
- at conf_permithosts = split /\s*,\s*/, $conf{'permithosts'};
-
-#
 # Allow user to override on commandline
 #
-while ($option = shift(@ARGV)) {
+while (my $option = shift(@ARGV)) {
   if ($option eq "-v") {
     $verbose = 1;
   } elsif ($option eq '-ldap') {
@@ -370,9 +395,9 @@
 		mylog( $syslog_priority, "Attribute: %s=%s", $_, $attr{$_});
 	    }
 	}
-	fatal_exit "unrecognized request type: '%s'", $attr{'request'}
+	fatal_exit("unrecognized request type: '".$attr{'request'}."'")
 	    unless $attr{'request'} eq "smtpd_access_policy";
-	$action = smtpd_access_policy();
+	my $action = smtpd_access_policy();
 	mylog( $syslog_priority, "Action: %s", $action) if $verbose;
 	print STDOUT "action=$action\n\n";
 	%attr = ();





More information about the commits mailing list