MailScanner, postfix and exchange server as a gateway - try these

Pete Russell pete at enitech.com.au
Wed Feb 22 11:05:07 GMT 2006


The postfix method of recipient list is superior to milterahead in that 
it doesnt rely on Exchange being up to function correctly, therefore it 
reduces the functionality of your gateway. IMO

Please find attched the script we plodded from some one else, fixed it 
up and used oursewlves.

1. It queries AD for ALL of the possible SMTP address for every user in 
the specified domain.
2. Check that you havent tried to create an empty file (nothing worse 
than a 0byte recipiuent map)
3. Writes the recipient map and runs postmap.
4. emails you any error messages so you know what the script is failing.

Simply add the lines to main.cf (we use multiple maps)

relay_recipient_maps = hash:/etc/postfix/1-relay_recipients,
                          hash:/etc/postfix/2-relay_recipients,

I have the scripts attached to queries Lotus Domino (we use R5) and MS 
AD (we use 2003)

You can see in the script its a sinmple matter to create ANY variation, 
or use regexp to write your maps to cover all the possible valid 
username variations for your company.

Obviously since we made these i have been looking at using regexp, which 
would make easy work of combining heaps of this script into something 
much smaller/smarter.

Hope it helps someone


Steve Freegard wrote:
> On Tue, 2006-02-21 at 11:51 +0000, Martin Hepworth wrote:
> 
>>For my view, better the devil you know...both are valid, as is exim or
>>qmail.
> 
> 
> Indeed - for Exim users - this:
> http://www.exim.org/mail-archives/exim-users/Week-of-Mon-20040816/msg00126.html might be a good alternative.
> 
> 
>>For sendmail you'll need to configure milter-ahead, see this..
>>http://www.fsl.com/support/Milter-Ahead-Exchange-Settings.pdf
> 
> 
> Note that milter-ahead will only work correctly with Exchange 2003 as
> it's the only version of Exchange that can be configured to actually
> *reject* invalid users.
> 
> Otherwise you have to fall back on the Postfix-style method and create a
> valid user list.
> 
> Cheers,
> Steve.
> 
-------------- next part --------------
#!/usr/bin/perl -w

# LOTUS DOMINO LDAP DIRECTORY - USERNAMES
# This script will pull all users' SMTP addresses from your Lotus Domino Directory
# and list them in the # format "user at example.com OK" which Postfix uses with 
# relay_recipient_maps.
# Be sure to double-check the path to perl above.

# This requires Net::LDAP to be installed.  To install Net::LDAP, at a shell
# type "perl -MCPAN -e shell" and then "install Net::LDAP"

use Net::LDAP;
use Mail::Mailer;
use Fcntl qw(:DEFAULT :flock);

# Enter the path to your Postfix relay_recipient_maps file
$RelayRecipientMaps = '/etc/postfix/1-relay_recipients';
$RecipientMaps = '1-relay_recipients';
# Script Number
$sno="Script 1";

# enter the path to the postmap command (or you MTAs equivelent)
$PostmapPath = "/usr/sbin/postmap";
# Enter the path to your log
$HistoryLog = '/etc/postfix/.1-ldap_count';

# Enter the tmp file path
$RandomValue = rand(9999) * rand(9999);
$TmpFile = '/etc/postfix/.1-ldap_tmp' . "$$" . ".$RandomValue";
if (-e $TmpFile) {
 # Something fishy is going on. Try another file name.
 $RandomValue = rand(9999999) * rand(9999999);
 $TmpFile = '/usr/local/etc/postfix/.1-ldap_tmp' . "$$" . ".$RandomValue";
 if (-e $TmpFile) {
  &ErrorLog("$sno - Temp file creation failed", "The tmp file $TmpFile already exists after two attempts at different file names. Update aborted.");
 }
}

# Enter the maximum variances permitted before the script will fai
$UCD="50";

# Enter the FQDN of your Lotus Domino Directory below
$dc1="notes.domain.com";
$dc2="10.1.10.4";

# Enter the LDAP container for your userbase.
# The syntax is CN=Users,dc=example,dc=com
# This can be found by installing the Windows 2000 Support Tools
# then running ADSI Edit.  
# I use Softerra LDAP Browser to nav the LDAP tree and work out base,
# username etc.
# LDAP://domaincontroller1.example.com/CN=Users,DC=example,DC=com 
# which would be $hqbase="cn=Users,dc=example,dc=com"
$hqbase="o=Domain";

# Enter the username & password for a valid user in your Domino Directory
# with username in the form cn=username
# Make sure the user's password does not expire.  Note that this user 
# does not require any special privileges.
# You can double-check this by typing the Internet Password
# in the users person doc.
# LDAP://domaincontroller1.example.com/CN=user,CN=Users,DC=example,DC=com 
# which would be $user="cn=user,cn=Users,dc=example,dc=com" 
$user="cn=Administrator";
$passwd="password";

# Enter the domain you want to append to your groupnames.
# we implemented this so we could write maps that included new subdomains etc
# that didnt appear in person doc info. (Global Domain docs)

$domain1="domain1.com.au";
$domain2="sub.domain1.com.au";
$domain3="domain1.edu.au";

# Postmaster email address - send all error messages here.
$postmaster='prussell at domain1.com.au';

# That's it, you're done.  (Unless you want to play with the LDAP filters below).




# TAB/SPACE you want to use to seperate the 
# email address and the permission eg prussell at domain1.com TAB/SPACE OK

$sep="\t";

# Type of permission, eg REJECT or OK
$perm="OK";
# Connecting to Lotus Domino Directory 
$noldapserver=0;
$ldap = Net::LDAP->new($dc1) or
   $noldapserver=1;
if ($noldapserver == 1)  {
   $ldap = Net::LDAP->new($dc2) or &ErrorLog("$sno - No LDAP Server", "Cannot Access the LDAP server $dc2");
}

$mesg = $ldap->bind ( dn => $user, password =>$passwd);

if ( $mesg->code()) {
 &ErrorLog("Bad Password", "The password was invalid. Updated aborted.");
}

$searchbase = $hqbase;

# Searching for users that are mail-enabled
$mesg = $ldap->search (base   => $searchbase,
                       filter => "(|(givenname=*)(sn=*)(shortname=*))",
                       attrs  => "mail");

$entries = $mesg->count;

if ($entries lt 1) {
#  die ($errormail);
        &ErrorLog("$sno - No LDAP queries matched your search", "No data was returned. Updated aborted");
	#die ("error:", Connection to LDAP successfull. But nothing matched your search criteria"\n");
}

my $UserCount = 0;
open(OUT,">$TmpFile");
flock(OUT, LOCK_EX);
# Filtering results for name variations.
foreach my $entry ( $mesg->entries ) {
   $UserCount++;

   # SHORT NAME VARIATIONS - This will collect ALL shortnames for all users.
   # prussell@, pruss@, pete@, russell@
     foreach my $tmp ( $entry->get_value( "shortname" ) ) {
     print OUT $tmp."\@$domain1$sep$perm\n";
     print OUT $tmp."\@$domain2$sep$perm\n";
     print OUT $tmp."\@$domain3$sep$perm\n";

   # First initial.lastname $tmp
   # p.russell@
     ($firstchar,$therest) = split(//,$tmp,2);
     $userwithdot = "$firstchar.$therest";
     print OUT $userwithdot."\@$domain1$sep$perm\n";
     print OUT $userwithdot."\@$domain2$sep$perm\n";    
   }

   # FULL NAME
   # pete.russell@
     $sn = $entry->get_value( "sn" );
     $fn = $entry->get_value( "givenname" );
     print OUT "$fn.$sn\@$domain1$sep$perm\n";
     print OUT "$fn.$sn\@$domain2$sep$perm\n";
}
#close(OUT);
flock(OUT, LOCK_UN);
close(OUT);

# Unbinding
$ldap->unbind;

if (!(-e $HistoryLog)) {
 # first time run, or someone erased our count file
# system("/usr/bin/touch","$HistoryLog");
system("/bin/echo 0 > $HistoryLog");
}
open(COUNT,"$HistoryLog") or &ErrorLog("$sno - History Log", "Unable to open $HistoryLog for reading: $!");
$CountLine = <COUNT>;
chomp($CountLine);
if ($CountLine =~ /^(?:\d+)$/) {
 if ($CountLine - $UserCount > $UCD) {
  &ErrorLog("$sno -  Results are down by $UCD", "Possible export corruption");
 }
} else { &ErrorLog("$sno -  Count file is corrupt", "LastCount file is corrupt"); }
close(COUNT);

open(COUNT,">$HistoryLog") or &ErrorLog("History Log", "Unable to open $HistoryLog for writing: $!");
seek(COUNT, 0, 0);
print COUNT "$UserCount\n";
close(COUNT);


if (-e "$RelayRecipientMaps") {
 if (-e "$RelayRecipientMaps.backup") {
  unlink("$RelayRecipientMaps.backup");
 }
}

system("/bin/cat $TmpFile > /usr/local/postfix/$RecipientMaps");
system("/bin/mv","$TmpFile","$RelayRecipientMaps");
system("$PostmapPath","$RelayRecipientMaps");
exit;


sub ErrorLog {
 $Subject = "$_[0]";
 $Message = "$_[1]";

 my $mail = Mail::Mailer->new("sendmail");
 $mail->open({
  "From" => "$sno ",
  "To" => "$postmaster",
  "Subject" => "$Subject"});
 print $mail "$Message\n";
 $mail->close();
 exit;
}
-------------- next part --------------
#!/usr/bin/perl -w

# This script will pull all users' SMTP addresses from your Active Directory
# (including primary and secondary email addresses) and list them in the
# format "user at example.com OK" which Postfix uses with relay_recipient_maps.
# Be sure to double-check the path to perl above.

# This requires Net::LDAP to be installed.  To install Net::LDAP, at a shell
# type "perl -MCPAN -e shell" and then "install Net::LDAP"

use Net::LDAP;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant ( "LDAP_CONTROL_PAGED" );

# Enter the path/file for the output
$VALID = "/root/5-relay_recipients";
open VALID, ">$VALID" or die "CANNOT OPEN $VALID $!";

$RecipientMaps = '5-relay_recipients';

# Enter the FQDN of your Active Directory domain controllers below
$dc1="10.1.10.8";
$dc2="10.2.2.32";

# Enter the LDAP container for your userbase.
# The syntax is CN=Users,dc=mbs,dc=edu
# This can be found by installing the Windows 2000 Support Tools
# then running ADSI Edit.
# In ADSI Edit, expand the "Domain NC [domaincontroller1.example.com]" &
# you will see, for example, DC=example,DC=com (this is your base).
# The Users Container will be specified in the right pane as
# CN=Users depending on your schema (this is your container).
# You can double-check this by clicking "Properties" of your user
# folder in ADSI Edit and examining the "Path" value, such as:
# LDAP://domaincontroller1.example.com/CN=Users,DC=example,DC=com
# which would be $hqbase="cn=Users,dc=example,dc=com"
# Note:  You can also use just $hqbase="dc=example,dc=com"
$hqbase="dc=domain,dc=local";

# Enter the username & password for a valid user in your Active Directory
# with username in the form cn=username,cn=Users,dc=example,dc=com
# Make sure the user's password does not expire.  Note that this user
# does not require any special privileges.
# You can double-check this by clicking "Properties" of your user in
# ADSI Edit and examining the "Path" value, such as:
# LDAP://domaincontroller1.example.com/CN=user,CN=Users,DC=example,DC=com
# which would be $user="cn=user,cn=Users,dc=example,dc=com"
# Note: You can also use the UPN login: "user\@example.com"
$user="CN=grice,OU=Public Accounts,OU=Enterprise,DC=domain,DC=local";
$passwd="password";

# Connecting to Active Directory domain controllers
$noldapserver=0;
$ldap = Net::LDAP->new($dc1) or
   $noldapserver=1;
if ($noldapserver == 1)  {
   $ldap = Net::LDAP->new($dc2) or
      die "Error connecting to specified domain controllers $@ \n";
}

$mesg = $ldap->bind ( dn => $user,
                      password =>$passwd);
if ( $mesg->code()) {
    die ("error:", $mesg->code(),"\n");
}

# How many LDAP query results to grab for each paged round
# Set to under 1000 for Active Directory
$page = Net::LDAP::Control::Paged->new( size => 990 );

@args = ( base     => $hqbase,
# Play around with this to grab objects such as Contacts, Public Folders, etc.
# A minimal filter for just users with email would be:
# filter => "(&(sAMAccountName=*)(mail=*))"
         filter => "(& (mailnickname=*) (| (&(objectCategory=person)
                    (objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))
                    (&(objectCategory=person)(objectClass=user)(|(homeMDB=*)
                    (msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=contact))
                    (objectCategory=group)(objectCategory=publicFolder) ))",
          control  => [ $page ],
          attrs  => "proxyAddresses",
);

my $cookie;
while(1) {
  # Perform search
  my $mesg = $ldap->search( @args );

# Filtering results for proxyAddresses attributes  
  foreach my $entry ( $mesg->entries ) {
    my $name = $entry->get_value( "cn" );
    # LDAP Attributes are multi-valued, so we have to print each one.
    foreach my $mail ( $entry->get_value( "proxyAddresses" ) ) {
     # Test if the Line starts with one of the following lines:
     # proxyAddresses: [smtp|SMTP]:
     # and also discard this starting string, so that $mail is only the
     # address without any other characters...
     if ( $mail =~ s/^(smtp|SMTP)://gs ) {
       print VALID $mail." \t OK\n"; 
     }
    }
  }

  # Only continue on LDAP_SUCCESS
  $mesg->code and last;

  # Get cookie from paged control
  my($resp)  = $mesg->control( LDAP_CONTROL_PAGED ) or last;
  $cookie    = $resp->cookie or last;

  # Set cookie in paged control
  $page->cookie($cookie);
}

if ($cookie) {
  # We had an abnormal exit, so let the server know we do not want any more
  $page->cookie($cookie);
  $page->size(0);
  $ldap->search( @args );
  # Also would be a good idea to die unhappily and inform OP at this point
     die("LDAP query unsuccessful");
}
# Add additional restrictions, users, etc. to the output file below.
#print VALID "user\@domain1.com OK\n";
#print VALID "user\@domain2.com 550 User unknown.\n";
#print VALID "domain3.com 550 User does not exist.\n";

close VALID;
system("/bin/cat $VALID > /usr/postfix/$RecipientMaps");
system("/usr/sbin/postmap","$VALID");



More information about the MailScanner mailing list