Category Archives: Postfix

Postfix queue monitoring script

Here is a small Perl script which can be useful as a Postfix queue monitor (count number of emails in Postfix queue)

#!/usr/bin/env perl
 
# postfix queue/s size
# author:
# source: http://tech.groups.yahoo.com/group/postfix-users/message/255133
 
use strict;
use warnings;
use Symbol;
sub count {
	my ($dir) = @_;
        my $dh = gensym();
        my $c = 0;
        opendir($dh, $dir) or die "$0: opendir: $dir: $!\n";
        while (my $f = readdir($dh)) {
                if ($f =~ m{^[A-F0-9]{5,}$}) {
                        ++$c;
                } elsif ($f =~ m{^[A-F0-9]$}) {
                        $c += count("$dir/$f");
                }
        }
	closedir($dh) or die "closedir: $dir: $!\n";
        return $c;
}
my $qdir = `postconf -h queue_directory`;
chomp($qdir);
chdir($qdir) or die "$0: chdir: $qdir: $!\n";
printf "Incoming: %d\n", count("incoming");
printf "Active: %d\n", count("active");
printf "Deferred: %d\n", count("deferred");
printf "Bounced: %d\n", count("bounce");
printf "Hold: %d\n", count("hold");
printf "Corrupt: %d\n", count("corrupt");

Save script as queueStatus (for example), chmod +x to make it exec, and output

[root@s1 tmp]# ./queueStatus 
Incoming: 0
Active: 0
Deferred: 8
Bounced: 6
Hold: 0
Corrupt: 0

Find and delete files

Few months ago, I had a problem after mail server migration. The old mail server decided to die and I had to replace the complete server. I read my logs carefully (on daily basis) and I noticed that hard disk will die so I prepared the complete backup before the damn thing decided to go to the hell.
The old server had the same software like the new one but Dovecot 2.x comes with disabled quota function.
Unfortunately I forget to check this which causes that after 2-3 days I run into problems with more than 15 000 email accounts. Deleted mails was not deducted from used quota and almost all mailboxes was marked as 100% used :(. The solution was to delete all maildirsize files inside mailboxes and let the Postfix to recreate this file as soon as the next email arrives.

As any other problem on Linux, this one also can be solved in one command 🙂

find . -type f -name "maildirsize" -exec rm -fv {} \;

Postfix quota notification email script

If you want to take care about your users quota and your storage space, check this …

1. Create new file (for example quota_notify inside /usr/local/sbin/ )

2. Copy next content inside quota_notify

#!/usr/bin/perl -w
 
# Author <jps@tntmax.com>
#
# This script assumes that virtual_mailbox_base in defined
# in postfix's main.cf file. This directory is assumed to contain
# directories which themselves contain your virtual user's maildirs.
# For example:
#
# -----------/
#            |
#            |
#    home/vmail/domains/
#        |          |
#        |          |
#  example.com/  foo.com/
#                   |
#                   |
#           -----------------
#           |       |       |
#           |       |       |
#         user1/   user2/  user3/
#                           |
#                           |
#                        maildirsize
#
 
use strict;
 
my $POSTFIX_CF = "/etc/postfix/main.cf";
my $MAILPROG = "/usr/sbin/sendmail -t";
my $WARNPERCENT = 80;
my @POSTMASTERS = ('postmaster@mydomain.tld');
my $CONAME = 'Company ....';
my $COADDR = 'postmaster@mydomain.tld';
my $SUADDR = 'postmaster@mydomain.tld';
my $MAIL_REPORT = 1;
my $MAIL_WARNING = 1;
 
#get virtual mailbox base from postfix config
open(PCF, "< $POSTFIX_CF") or die $!;
my $mboxBase;
while (<PCF>) {
   next unless /virtual_mailbox_base\s*=\s*(.*)\s*/;
   $mboxBase = $1;
}
close(PCF);
 
#assume one level of subdirectories for domain names
my @domains;
opendir(DIR, $mboxBase) or die $!;
while (defined(my $name = readdir(DIR))) {
   next if $name =~ /^\.\.?$/;        #skip '.' and '..'
   next unless (-d "$mboxBase/$name");
   push(@domains, $name);
}
closedir(DIR);
#iterate through domains for username/maildirsize files
my @users;
chdir($mboxBase);
foreach my $domain (@domains) {
        opendir(DIR, $domain) or die $!;
        while (defined(my $name = readdir(DIR))) {
           next if $name =~ /^\.\.?$/;        #skip '.' and '..'
           next unless (-d "$domain/$name");
      push(@users, {"$name\@$domain" => "$mboxBase/$domain/$name"});
        }
}
closedir(DIR);
 
 
#get user quotas and percent used
my (%lusers, $report);
foreach my $href (@users) {
   foreach my $user (keys %$href) {
      my $quotafile = "$href->{$user}/maildirsize";
      next unless (-f $quotafile);
      open(QF, "< $quotafile") or die $!;
      my ($firstln, $quota, $used);
      while (<QF>) {
         my $line = $_;
              if (! $firstln) {
                 $firstln = 1;
                 die "Error: corrupt quotafile $quotafile"
                    unless ($line =~ /^(\d+)S/);
                 $quota = $1;
            last if (! $quota);
            next;
         }
         die "Error: corrupt quotafile $quotafile"
            unless ($line =~ /\s*(-?\d+)/);
         $used += $1;
      }
      close(QF);
      next if (! $used);
      my $percent = int($used / $quota * 100);
      $lusers{$user} = $percent unless not $percent;
   }
}
 
 
#send a report to the postmasters
if ($MAIL_REPORT) {
   open(MAIL, "| $MAILPROG");
   select(MAIL);
   map {print "To: $_\n"} @POSTMASTERS;
   print "From: $COADDR\n";
   print "Subject: Daily Quota Report.\n";
   print "DAILY QUOTA REPORT:\n\n";
   print "----------------------------------------------\n";
   print "| % USAGE |            ACCOUNT NAME          |\n";
   print "----------------------------------------------\n";
   foreach my $luser ( sort { $lusers{$b} <=> $lusers{$a} } keys %lusers ) {
      printf("|   %3d   | %32s |\n", $lusers{$luser}, $luser);
      print "---------------------------------------------\n";
   }
        print "\n--\n";
        print "$CONAME\n";
        close(MAIL);
}
 
#email a warning to people over quota
if ($MAIL_WARNING) {
        foreach my $luser (keys (%lusers)) {
           next unless $lusers{$luser} >= $WARNPERCENT;       # skip those under quota
           open(MAIL, "| $MAILPROG");
           select(MAIL);
           print "To: $luser\n";
      map {print "BCC: $_\n"} @POSTMASTERS;
           print "From: $SUADDR\n";
           print "Subject: WARNING: Your mailbox is $lusers{$luser}% full.\n";
           print "Reply-to: $SUADDR\n";
           print "Your mailbox: $luser is $lusers{$luser}% full.\n\n";
           print "Please consider deleting e-mail and emptying your trash folder to clear some space.\n\n";
           print "Contact <$SUADDR> for further assistance.\n\n";
           print "Thank You.\n\n";
           print "--\n";
           print "$CONAME\n";
           close(MAIL);
        }
}

3. Edit next lines inside and set up your postmaster email address, percentage, etc

my $POSTFIX_CF = "/etc/postfix/main.cf";
my $MAILPROG = "/usr/sbin/sendmail -t";
my $WARNPERCENT = 80;
my @POSTMASTERS = ('postmaster@mydomain.tld');
my $CONAME = 'Company ....';
my $COADDR = 'postmaster@mydomain.tld';
my $SUADDR = 'postmaster@mydomain.tld';
my $MAIL_REPORT = 1;
my $MAIL_WARNING = 1;

4. set chmod 755 on quota_notify (# chmod 755 /usr/local/sbin/quota_notify)

5. add next line to crontab so it will be executed at 0:00 every day.

0 0 * * * /usr/local/sbin/quota_notify &> /dev/null

Keep in mind that this script has only reporting purpose and it will not reject any email when user mailbox is over quota. For this you will need Postfix with quota patch.

This post was inspired by this LINK.

Releasing a message from a quarantine with amavisd-relase

amavisd-new is a high-performance and reliable interface between mailer (MTA) and one or more content checkers: virus scanners, and/or Mail::SpamAssassin Perl module. It is written in Perl, ensuring high reliability, portability and maintainability. It talks to MTA via (E)SMTP or LMTP protocols, or by using helper programs. No timing gaps exist in the design, which could cause a mail loss.

In other words, amavisd-new will help you to fight against spam. In this post, I won’t write about installation (coming soon in you theaters)

This post is just a small trick which will help you to release specific message from quarantine (false positive or you simple want to read spam messages)

Fist you need to find message inside the messages log file (usually /var/log/messages)

May 10 10:06:56 ns1 amavis[12774]: (12774-13) Blocked SPAM, [207.46.22.35] [207.46.22.35] <cnfrmpro@microsoft.com> -> <mymail@domain.tld>, quarantine: spam-1lvc624m6MVB.gz, Message-ID: <BY2MSFTVSMTP03Dfn8e0003d305@by2msftvsmtp03.phx.gbl>, mail_id: 1lvc624m6MVB, Hits: 7.743, size: 3013, 4325 ms

As you can see above, it is spam-1lvc624m6MVB.gz

Now you can release specific message with

[root@s1 ~]# amavisd-release spam-1lvc624m6MVB.gz

And you will see something like

250 2.0.0 Ok, id=rel-1lvc624m6MVB, from MTA([127.0.0.1]:10025): 250 2.0.0 Ok: queued as 403206AF07CE

Now you just need to check your inbox and you should see the message.

Config mta – howto

Centos has a neat application for switching between alternative software packages, called alternatives.

Few days ago I noticed that one server doesn’t send logwatch email. I wanted to see what was the reason and here are the few tips you can check before you dig inside logwatch settings.

First, check /etc/aliases and root email inside

# nano /etc/aliases

at the end, check next lines:

# Person who should get root's mail
root:           blabla@domain.tld

After you save aliases, enter command

# newaliases

This command rebuilds the random access data base for the mail aliases file /etc/aliases. It must be run each time this file is changed in order for the change to take effect. This would be enough to receive all email directed to root, but in case you still don’t get root emails, check mta with:

# alternatives --display mta

This will show you something like

[root@s1 ~]# alternatives --display mta
mta - status is manual.
 link currently points to /usr/sbin/sendmail.sendmail
/usr/sbin/sendmail.sendmail - priority 90
 slave mta-pam: /etc/pam.d/smtp.sendmail
 slave mta-mailq: /usr/bin/mailq.sendmail
 slave mta-newaliases: /usr/bin/newaliases.sendmail
 slave mta-rmail: /usr/bin/rmail.sendmail
 slave mta-sendmail: /usr/lib/sendmail.sendmail
 slave mta-mailqman: /usr/share/man/man1/mailq.sendmail.1.gz
 slave mta-newaliasesman: /usr/share/man/man1/newaliases.sendmail.1.gz
 slave mta-aliasesman: /usr/share/man/man5/aliases.sendmail.5.gz
 slave mta-sendmailman: /usr/share/man/man8/sendmail.sendmail.8.gz
/usr/sbin/sendmail.postfix - priority 30
 slave mta-pam: /etc/pam.d/smtp.postfix
 slave mta-mailq: /usr/bin/mailq.postfix
 slave mta-newaliases: /usr/bin/newaliases.postfix
 slave mta-rmail: /usr/bin/rmail.postfix
 slave mta-sendmail: /usr/lib/sendmail.postfix
 slave mta-mailqman: /usr/share/man/man1/mailq.postfix.1.gz
 slave mta-newaliasesman: /usr/share/man/man1/newaliases.postfix.1.gz
 slave mta-aliasesman: /usr/share/man/man5/aliases.postfix.5.gz
 slave mta-sendmailman: /usr/share/man/man1/sendmail.postfix.1.gz
Current `best' version is /usr/sbin/sendmail.sendmail.

We use postfix so we should change this with

[root@s1 ~]# alternatives --config mta
 
There are 2 programs which provide 'mta'.
 
  Selection    Command
-----------------------------------------------
*+ 1           /usr/sbin/sendmail.sendmail
   2           /usr/sbin/sendmail.postfix
 
Enter to keep the current selection[+], or type selection number:

Enter 2 and press Enter. Then check your mta with

[root@s1 ~]# alternatives --display mta
mta - status is manual.
 link currently points to /usr/sbin/sendmail.postfix

That’s it… Now if your logwatch is configured properly you should receive root emails…

How to whitelist hosts or IP addresses in Postfix

If you are administrating a mail server and use blacklists to block spam, sometimes you may have a problem with certain mail servers. This happens because a specific mail server was blacklisted. You can see that one server was blacklisted if you trace your maillog:

reject: RCPT from unknown[196.206.244.208]: 554 5.7.1 Service unavailable; Client host [196.206.244.208] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?196.206.244.208; from=<laya@mymail.com> to=<laya@mymail.com> proto=SMTP helo=<aimp.org>

In this example, the mail server 196.206.244.208 is blacklisted and therefore blocked (also in this case, message was spam and we won’t whitelist 196.206.244.208).

To whitelist servers, we need one file (for example /etc/postfix/rbl_whitelist) where we will list all IP addresses or host names marked for whitelist.

# nano /etc/postfix/rbl_whitelist

Every line should contain only one IP address or one hostname in next format

196.196.196.196 OK
mail.mymail.com OK

Save file and then run:

# postmap /etc/postfix/rbl_whitelist

After you created whitelist in postfix format, open /etc/postfix/main.cf and search for the smtpd_recipient_restrictions parameter. Add
check_client_access hash:/etc/postfix/rbl_whitelist
after reject_unauth_destination, but before the first blacklist.

Remember BEFORE the first blacklist or this won’t work.

smtpd_recipient_restrictions = reject_invalid_hostname,
                               reject_unauth_pipelining,
                               permit_mynetworks,
                               permit_sasl_authenticated,
                               reject_unauth_destination,
                               check_client_access hash:/etc/postfix/rbl_whitelist,
                               reject_rbl_client dsn.rfc-ignorant.org,
                               reject_rbl_client dul.dnsbl.sorbs.net,
                               reject_rbl_client sbl-xbl.spamhaus.org,
                               reject_rbl_client bl.spamcop.net,
                               permit

The lines shown above is only example. Please check all those blacklist because some of them are not active any more….

And finally reload postfix with

# service postfix restart

or

# /etc/init.d/postfix restart

Edit
Remember that smtpd_recipient_restrictions section mentioned above is just for reference. Please double check this blacklists before you use them. (Some of them doesn’t work any more). Especially if you find this post 3 years after I wrote it…