TigerKR

Q: OS X 10.9 Server Mail Domain Whitelisting

I just cannot understand why this isn't working.

 

I've added gmail, googlemail, mac, me, icloud to the whitelist, but emails from those domains still get greylisted:

$ sudo serveradmin settings Mail | grep whitelist

Mail:postfix:add_whitelist_domain:_array_index:5 = "gmail.com"

Mail:postfix:add_whitelist_domain:_array_index:6 = "googlemail.com"

Mail:postfix:add_whitelist_domain:_array_index:7 = "mac.com"

Mail:postfix:add_whitelist_domain:_array_index:8 = "me.com"

Mail:postfix:add_whitelist_domain:_array_index:9 = "icloud.com"

Mail:postfix:domain_whitelist:_array_index:5 = "gmail.com"

Mail:postfix:domain_whitelist:_array_index:6 = "googlemail.com"

Mail:postfix:domain_whitelist:_array_index:7 = "mac.com"

Mail:postfix:domain_whitelist:_array_index:8 = "me.com"

Mail:postfix:domain_whitelist:_array_index:9 = "icloud.com"

I tried:

$ sudo serveradmin settings Mail:postfix:whitelist_enabled = yes

2014-05-02 02:56:37.103 serveradmin[2894:507] Error: servermgr_mail: postconf read error: /usr/sbin/postconf: warning: whitelist_enabled: unknown parameter

2014-05-02 02:56:37.104 serveradmin[2894:507] unable to save key: whitelist_enabled (unknown parameter)

I've tried restarting the mail service and restarting the computer.

 

Has anyone been successful in whitelisting a domain on 10.9 Server?

May  2 02:37:47 example.com postfix/smtpd[1398]: connect from mail-oa0-f48.google.com[209.85.219.48]

May  2 02:37:47 example /usr/libexec/postfix/greylist.pl[1403]: Temporary message rejection to: <me@example.com> from: <johnny.appleseed@gmail.com> sent from: [209.85.219.48] for: 60 seconds due to greylisting

May  2 02:37:48 example.com postfix/smtpd[1398]: NOQUEUE: reject: RCPT from mail-oa0-f48.google.com[209.85.219.48]: 450 4.7.1 <me@example.com>: Recipient address rejected: Service is unavailable; from=<johnny.appleseed@gmail.com> to=<me@example.com> proto=ESMTP helo=<mail-oa0-f48.google.com>

May  2 02:37:48 example.com postfix/smtpd[1398]: disconnect from mail-oa0-f48.google.com[209.85.219.48]

Posted on May 2, 2014 12:06 AM

Close

Q: OS X 10.9 Server Mail Domain Whitelisting

  • All replies
  • Helpful answers

  • by Gerben Wierda,

    Gerben Wierda Gerben Wierda May 9, 2014 4:41 AM in response to TigerKR
    Level 2 (185 points)
    Mac OS X
    May 9, 2014 4:41 AM in response to TigerKR

    Can you post the 10.9 version of greylist.pl somewhere so I can inspect it? Here is OK.

  • by TigerKR,

    TigerKR TigerKR May 11, 2014 12:06 AM in response to Gerben Wierda
    Level 1 (30 points)
    May 11, 2014 12:06 AM in response to Gerben Wierda

    #!/usr/bin/perl

     

    use DB_File;

    use Fcntl;

    use Sys::Syslog qw(:DEFAULT setlogsock);

     

    #

    # Usage: greylist.pl [-v]

    #

    # Demo delegated Postfix SMTPD policy server. This server implements

    # greylisting. State is kept in a Berkeley DB database.  Logging is

    # sent to syslogd.

    #

    # How it works: each time a Postfix SMTP server process is started

    # it connects to the policy service socket, and Postfix runs one

    # instance of this PERL script.  By default, a Postfix SMTP server

    # process terminates after 100 seconds of idle time, or after serving

    # 100 clients. Thus, the cost of starting this PERL script is smoothed

    # out over time.

    #

    # To run this from /etc/postfix/master.cf:

    #

    #    policy  unix  -       n       n       -       -       spawn

    #      user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl

    #

    # To use this from Postfix SMTPD, use in /etc/postfix/main.cf:

    #

    #    smtpd_recipient_restrictions =

    #          ...

    #          reject_unauth_destination

    #          check_policy_service unix:private/policy

    #          ...

    #

    # NOTE: specify check_policy_service AFTER reject_unauth_destination

    # or else your system can become an open relay.

    #

    # To test this script by hand, execute:

    #

    #    % perl greylist.pl

    #

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

    #

    #    request=smtpd_access_policy

    #    protocol_state=RCPT

    #    protocol_name=SMTP

    #    helo_name=some.domain.tld

    #    queue_id=8045F2AB23

    #    sender=foo@bar.tld

    #    recipient=bar@foo.tld

    #    client_address=1.2.3.4

    #    client_name=another.domain.tld

    #    instance=123.456.7

    #    sasl_method=plain

    #    sasl_username=you

    #    sasl_sender=

    #    size=12345

    #    [empty line]

    #

    # The policy server script will answer in the same style, with an

    # attribute list followed by a empty line:

    #

    #    action=dunno

    #    [empty line]

    #

     

    #

    # greylist status database and greylist time interval. DO NOT create the

    # greylist status database in a world-writable directory such as /tmp

    # or /var/tmp. DO NOT create the greylist database in a file system

    # that can run out of space.

    #

    # In case of database corruption, this script saves the database as

    # $database_name.time(), so that the mail system does not get stuck.

    #

    $database_name="/Library/Server/Mail/Data/gldb/greylist.db";

    $whitelist_host_file="/Library/Server/Mail/Data/gldb/whitelist_host";

    $whitelist_domain_file="/Library/Server/Mail/Data/gldb/whitelist_domain";

    $whitelist_db_name="/Library/Server/Mail/Data/gldb/whitelist.db";

    $greylist_delay=60;

     

    #

    # Auto-whitelist threshold. Specify 0 to disable, or the number of

    # successful "come backs" after which a client is no longer subject

    # to greylisting.

    #

    $auto_whitelist_threshold = 10;

     

    #

    # Syslogging options for verbose mode and for fatal errors.

    # NOTE: comment out the $syslog_socktype line if syslogging does not

    # work on your system.

    #

    $syslog_socktype = 'unix'; # inet, unix, stream, console

    $syslog_facility="mail";

    $syslog_options="pid";

    $syslog_priority="info";

     

    sub add_whitelist {

              # check for null host name

              my ($_host_name) = $attr{"host_name"};

              if ($_host_name eq "") {

                        syslog $syslog_priority, "Warning: missing whitelist host name attribute";

                        return 0;

              }

     

              # Open the database on the fly.

              open_whitelist_db() unless $whitelist_db_obj;

     

              # Is host already in white list

              $value = read_whitelist_db($attr{"host_name"});

     

              # Add host if not in database

              if ($value == 0) {

                        syslog $syslog_priority, "adding host: %s to whitelist host", $attr{"host_name"} if $verbose;

                        update_whitelist_db($attr{"host_name"}, 1);

     

                        open WHITELIST_FILE, ">> $whitelist_host_file" or

                                  syslog $syslog_priority, "Error: unable to open whitelist host file: %s", $whitelist_host_file;

                        print WHITELIST_FILE "$attr{\"host_name\"}\n";

                        close WHITELIST_FILE;

              }

    }

     

    sub add_whitelist_domain {

              # check for null host name

              my ($_domain_name) = $attr{"domain_name"};

              if ($_domain_name eq "") {

                        syslog $syslog_priority, "Warning: missing whitelist domain name attribute";

                        return 0;

              }

     

              # Open the database on the fly.

              open_whitelist_db() unless $whitelist_db_obj;

     

              # Is domain already in white list

              $value = read_whitelist_db($attr{"domain_name"});

     

              # Add domain if not in database

              if ($value == 0) {

                        syslog $syslog_priority, "adding domain: %s to whitelist doman", $attr{"domain_name"} if $verbose;

                        update_whitelist_db($attr{"domain_name"}, 1);

     

                        open WHITELIST_FILE, ">> $whitelist_domain_file" or

                                  syslog $syslog_priority, "Error: unable to open whitelist domain file: %s", $whitelist_domain_file;

                        print WHITELIST_FILE "$attr{\"domain_name\"}\n";

                        close WHITELIST_FILE;

              }

    }

     

    #

    # Demo 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 {

        my($key, $time_stamp, $now, $count, $domain);

     

        # Open the database on the fly.

        open_database() unless $database_obj;

     

        # Open the whitelist database on the fly.

        open_whitelist_db() unless $whitelist_db_obj;

     

        # Check if domain is whitelisted

              $domain = get_domain_name($attr{"client_name"});

              $count = read_whitelist_db($domain);

              if ($count > 0) {

                        syslog $syslog_priority, "domain: %s is whitelisted", $domain if $verbose;

                  return "dunno";

              }

     

        # Check if host is whitelisted

              $count = read_whitelist_db($attr{"client_name"});

              if ($count > 0) {

                        syslog $syslog_priority, "host: %s is whitelisted", $attr{"client_name"} if $verbose;

                  return "dunno";

              }

     

        # Search the auto-whitelist.

        if ($auto_whitelist_threshold > 0) {

            $count = read_database($attr{"client_address"});

            if ($count > $auto_whitelist_threshold) {

                  return "dunno";

            }

        }

     

        # Lookup the time stamp for this client/sender/recipient.

        $key =

              lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};

        $time_stamp = read_database($key);

        $now = time();

     

        # If this is a new request add this client/sender/recipient to the database.

        if ($time_stamp == 0) {

              $time_stamp = $now;

              update_database($key, $time_stamp);

        }

     

        # The result can be any action that is allowed in a Postfix access(5) map.

        #

        # To label mail, return ``PREPEND'' headername: headertext

        #

        # In case of success, return ``DUNNO'' instead of ``OK'' so that the

        # check_policy_service restriction can be followed by other restrictions.

        #

        # In case of failure, specify ``DEFER_IF_PERMIT optional text...''

        # so that mail can still be blocked by other access restrictions.

        #

        syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;

        if ($now - $time_stamp > $greylist_delay) {

              # Update the auto-whitelist.

              if ($auto_whitelist_threshold > 0) {

                  update_database($attr{"client_address"}, $count + 1);

              }

              return "dunno";

        } else {

              # Apple

              syslog $syslog_priority, "Temporary message rejection to: <$attr{\"recipient\"}> from: <$attr{\"sender\"}> sent from: [$attr{\"client_address\"}] for: $greylist_delay seconds due to greylisting";

              return "defer_if_permit Service is unavailable";

        }

    }

     

    #

    # You should not have to make changes below this point.

    #

    sub LOCK_SH { 1 };          # Shared lock (used for reading).

    sub LOCK_EX { 2 };          # Exclusive lock (used for writing).

    sub LOCK_NB { 4 };          # Don't block (for testing).

    sub LOCK_UN { 8 };          # Release lock.

     

    #

    # Log an error and abort.

    #

    sub fatal_exit {

        my($first) = shift(@_);

        syslog "err", "fatal: $first", @_;

        exit 1;

    }

     

    #

    # Open hash database.

    #

    sub open_database {

        my($database_fd);

     

        # Use tied database to make complex manipulations easier to express.

        $database_obj = tie(%db_hash, 'DB_File', $database_name,

                                            O_CREAT|O_RDWR, 0644, $DB_BTREE);

              if ( !$database_obj ) {

                        # don't prevent mail deliveries due to corrupt database

                        my $db_backup = $database_name . "." . time();

                        syslog $syslog_priority, "Warning: open failed for: %s : backing up to: %s",

                                                                                              $database_name, $db_backup;

                        rename $database_name, $db_backup ||

                                  fatal_exit "Can't save %s as %s: $!", $database_name, $db_backup;

     

                        # try again

                        $database_obj = tie(%db_hash, 'DB_File', $database_name,

                                                      O_CREAT|O_RDWR, 0644, $DB_BTREE) ||

                                                      fatal_exit "Cannot open database %s: $!", $database_name;

              }

        $database_fd = $database_obj->fd;

        open DATABASE_HANDLE, "+<&=$database_fd" ||

              fatal_exit "Cannot fdopen database %s: $!", $database_name;

        syslog $syslog_priority, "open %s", $database_name if $verbose;

    }

     

    #

    # Open hash whitelist database.

    #

    sub open_whitelist_db {

        my($whitelist_db_fd);

     

        # Use tied database to make complex manipulations easier to express.

              $whitelist_db_obj = tie(%db_hash, 'DB_File', $whitelist_db_name,

                                            O_CREAT|O_RDWR, 0644, $DB_BTREE);

              if ( !$whitelist_db_obj ) {

                        # don't prevent mail deliveries due to corrupt database

                        my $db_backup = $whitelist_db_name . "." . time();

                        syslog $syslog_priority, "Warning: open failed for: %s : backing up to: %s",

                                                                                              $whitelist_db_name, $db_backup;

                        rename $whitelist_db_name, $db_backup ||

                                  fatal_exit "Can't save %s as %s: $!", $whitelist_db_name, $db_backup;

     

                        # try again

                        $whitelist_db_obj = tie(%db_hash, 'DB_File', $whitelist_db_name,

                                                      O_CREAT|O_RDWR, 0644, $DB_BTREE) ||

                                                      fatal_exit "Cannot open database %s: $!", $whitelist_db_name;

              }

        $whitelist_db_fd = $whitelist_db_obj->fd;

        open WHITELIST_DB_HANDLE, "+<&=$whitelist_db_fd" ||

              fatal_exit "Cannot fdopen database %s: $!", $whitelist_db_name;

        syslog $syslog_priority, "open %s", $whitelist_db_name if $verbose;

    }

     

    #

    # Read database. Use a shared lock to avoid reading the database

    # while it is being changed. XXX There should be a way to synchronize

    # our cache from the on-file database before looking up the key.

    #

    sub read_database {

        my($key) = @_;

        my($value);

     

        flock DATABASE_HANDLE, LOCK_SH ||

              fatal_exit "Can't get shared lock on %s: $!", $database_name;

        # XXX Synchronize our cache from the on-disk copy before lookup.

        $value = $db_hash{$key};

        syslog $syslog_priority, "lookup %s: %s", $key, $value if $verbose;

        flock DATABASE_HANDLE, LOCK_UN ||

              fatal_exit "Can't unlock %s: $!", $database_name;

        return $value;

    }

     

    #

    # Read database. Use a shared lock to avoid reading the database

    # while it is being changed. XXX There should be a way to synchronize

    # our cache from the on-file database before looking up the key.

    #

    sub read_whitelist_db {

        my($key) = @_;

        my($value);

     

        flock WHITELIST_DB_HANDLE, LOCK_SH ||

              fatal_exit "Can't get shared lock on %s: $!", $whitelist_db_name;

        # XXX Synchronize our cache from the on-disk copy before lookup.

        $value = $db_hash{$key};

        syslog $syslog_priority, "whitelist lookup %s: %s", $key, $value if $verbose;

        flock WHITELIST_DB_HANDLE, LOCK_UN ||

              fatal_exit "Can't unlock %s: $!", $whitelist_db_name;

        return $value;

    }

     

    #

    # Update database. Use an exclusive lock to avoid collisions with

    # other updaters, and to avoid surprises in database readers. XXX

    # There should be a way to synchronize our cache from the on-file

    # database before updating the database.

    #

    sub update_database {

        my($key, $value) = @_;

     

        syslog $syslog_priority, "store %s: %s", $key, $value if $verbose;

        flock DATABASE_HANDLE, LOCK_EX ||

              fatal_exit "Can't exclusively lock %s: $!", $database_name;

        # XXX Synchronize our cache from the on-disk copy before update.

        $db_hash{$key} = $value;

        $database_obj->sync() &&

              fatal_exit "Can't update %s: $!", $database_name;

        flock DATABASE_HANDLE, LOCK_UN ||

              fatal_exit "Can't unlock %s: $!", $database_name;

    }

     

    #

    # Update database. Use an exclusive lock to avoid collisions with

    # other updaters, and to avoid surprises in database readers. XXX

    # There should be a way to synchronize our cache from the on-file

    # database before updating the database.

    #

    sub update_whitelist_db {

        my($key, $value) = @_;

     

        syslog $syslog_priority, "store whitelist host %s: %s", $key, $value if $verbose;

        flock WHITELIST_DB_HANDLE, LOCK_EX ||

              fatal_exit "Can't exclusively lock %s: $!", $whitelist_db_name;

        # XXX Synchronize our cache from the on-disk copy before update.

        $db_hash{$key} = $value;

        $whitelist_db_obj->sync() &&

              fatal_exit "Can't update %s: $!", $whitelist_db_name;

        flock WHITELIST_DB_HANDLE, LOCK_UN ||

              fatal_exit "Can't unlock %s: $!", $whitelist_db_name;

    }

     

    #

    # Parse hostname to obtain domain name

    #

    sub get_domain_name {

        my($in_host_name) = @_;

        my($value);

              my($count) = 0;

     

              @tokens = split(/\./, $in_host_name);

              $count = $#tokens;

              $value=$tokens[$count-1] . "." . $tokens[$count];

              return $value;

    }

     

    #

    # Signal 11 means that we have some kind of database corruption (yes

    # Berkeley DB should handle this better).  Move the corrupted database

    # out of the way, and start with a new database.

    #

    sub sigsegv_handler {

        my $backup = $database_name . "." . time();

     

        rename $database_name, $backup ||

              fatal_exit "Can't save %s as %s: $!", $database_name, $backup;

        fatal_exit "Caught signal 11; the corrupted database is saved as $backup";

     

        my $wl_backup = $whitelist_db_name . "." . time();

     

        rename $whitelist_db_name, $wl_backup ||

              fatal_exit "Can't save %s as %s: $!", $whitelist_db_name, $wl_backup;

        fatal_exit "Caught signal 11; the corrupted database is saved as $wl_backup";

    }

     

    $SIG{'SEGV'} = 'sigsegv_handler';

     

    #

    # This process runs as a daemon, so it can't log to a terminal. Use

    # syslog so that people can actually see our messages.

    #

    setlogsock $syslog_socktype;

    openlog $0, $syslog_options, $syslog_facility;

     

    #

    # We don't need getopt() for now.

    #

    while ($option = shift(@ARGV)) {

        if ($option eq "-v") {

              $verbose = 1;

        } else {

              syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",

                        $option, $0;

              exit 1;

        }

    }

     

    #

    # Unbuffer standard output.

    #

    select((select(STDOUT), $| = 1)[0]);

     

    #

    # Receive a bunch of attributes, evaluate the policy, send the result.

    #

    while (<STDIN>) {

        if (/([^=]+)=(.*)\n/) {

              $attr{substr($1, 0, 512)} = substr($2, 0, 512);

        } elsif ($_ eq "\n") {

              if ($verbose) {

                  for (keys %attr) {

                        syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};

                  }

              }

                        if ( $attr{"request"} eq "smtpd_access_policy" ) {

                                  $action = smtpd_access_policy();

                        } elsif ( $attr{"request"} eq "whitelist" ) {

                                  $action = add_whitelist();

                        } elsif ( $attr{"request"} eq "whitelist_domain" ) {

                                  $action = add_whitelist_domain();

                        } else {

                                  fatal_exit "unrecognized request type: '%s'", $attr{request};

                        }

                        syslog $syslog_priority, "Action: %s", $action if $verbose;

                        print STDOUT "action=$action\n\n";

                        %attr = ();

        } else {

              chop;

              syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;

        }

    }

  • by Gerben Wierda,

    Gerben Wierda Gerben Wierda May 11, 2014 1:38 AM in response to TigerKR
    Level 2 (185 points)
    Mac OS X
    May 11, 2014 1:38 AM in response to TigerKR

    They seem to be identical.

  • by Gerben Wierda,

    Gerben Wierda Gerben Wierda May 11, 2014 1:48 AM in response to TigerKR
    Level 2 (185 points)
    Mac OS X
    May 11, 2014 1:48 AM in response to TigerKR

    Hmm, maybe it did not work for me too when I set that up. It might be I ended up adapting postfix settings to circumvent greyllisting for these domains as I have this in my main.cf

     

    smtpd_recipient_restrictions = permit_sasl_authenticated permit_mynetworks reject_unauth_destination check_sender_access hash:/Library/Server/Mail/Config/postfix/policy_whitelist_senders check_policy_service unix:private/policy permit

     

    Add the domains to the file /Library/Server/Mail/Config/postfix/policy_whitelist_senders (each line: domain name followed by whitespace followed by OK) and use the postmap command to make a hash table. This way, these senders do not end up in greylisting.

     

    I also have some RBL exceptions (client MTA's that doe not go via RBL):

     

    main.cf:smtpd_client_restrictions = permit_mynetworks permit_sasl_authenticated check_client_access hash:/Library/Server/Mail/Config/postfix/rbl_whitelist_clients reject_rbl_client zen.spamhaus.org permit

     

    (same format for rbl_whitelist_clients).