Limiting outgoing messages on a server with postfix and postfwd

    On the server where the sites of various users are located, most often it is possible to send messages through the local mail server. If one of the sites is hacked, it is possible to send spam messages, which can lead to serious consequences, such as entering the IP address of the mail server in the blocking lists.

    In order to prevent the possibility of discrediting the mail server, it is necessary to limit the maximum number of outgoing messages that each user can send in a certain period of time.

    The problem is not absolutely trivial, and the purpose of this article is to demonstrate a ready-made solution that can be applied in production.

    Initial data: OS Debian 8 ("Jessie"), Postfix mail server.

    Install and configure postfwd package


    Install the postfwd package using apt:

    apt-get install postfwd
    

    The package does not have a configuration file and does not start by default. Create a configuration file /etc/postfix/postfwd.cf and add a rule (sending no more than 50 messages per hour for one user):

    id=R001; action=rcpt(sender/50/3600/REJECT limit of 50 recipients per hour for sender $$sender exceeded)
    

    You can read more about postfwd configuration in the documentation on the project website .

    Edit the file / etc / default / postfwd :

    # Разрешить запуск демона
    STARTUP=1
    # Путь к файлу, где содержатся правила
    CONF=/etc/postfix/postfwd.cf
    # IP адрес, на котором демон будет слушать входящие сообщения
    INET=127.0.0.1
    # Порт, на котором демон будет слушать входящие сообщения
    PORT=10040
    # Пользователь, от которого работает демон
    RUNAS="nobody"
    # Аргументы, которые передаются демону при старте
    ARGS="--summary=600 --cache=600 --cache-rdomain-only --cache-no-size"
    

    Restart the postfwd service:

    service postfwd restart
    

    And check that everything works as it should:

    srv1:~# tail /var/log/mail.log
    Jun  9 14:14:18 srv1 postfwd2/master[37242]: postfwd2 1.35 starting
    Jun  9 14:14:18 srv1 postfwd2/master[37244]: Started cache at pid 37245
    Jun  9 14:14:18 srv1 postfwd2/master[37244]: Started server at pid 37246
    Jun  9 14:14:18 srv1 postfwd2/cache[37245]: ready for input
    Jun  9 14:14:18 srv1 postfwd2/policy[37246]: ready for input
    

    Postfix and postfwd integration


    In order to integrate Postfix mail server and postfwd service, it is necessary to modify the configuration file /etc/postfix/main.cf , adding the following parameters to it:

    # Подключаем наш policy service - postfwd и разрешаем отправку только авторизированным пользователям
    smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:10040, permit_sasl_authenticated, reject_unauth_destination
    # Подключаем наш policy service - postfwd
    smtpd_end_of_data_restrictions = check_policy_service inet:127.0.0.1:10040
    

    Connecting posftwd should be done at the beginning, this is due to the interpretation of the parameters by the Postfix mail server. If the smtpd_recipient_restrictions and smtpd_end_of_data_restrictions parameters are already set in the configuration file, you need to change them so that check_policy_service inet: 127.0.0.1: 10040 and permit_sasl_authenticated are located at the beginning.

    Reload the configuration:

    srv1:~# postfix reload
    postfix/postfix-script: refreshing the Postfix mail system
    

    After the reboot, postfwd starts its work and periodically displays statistics in the mail log (/var/log/mail.log):

    srv1:~# tail /var/log/mail.log | grep postfwd
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS] postfwd2::policy 1.35: 1 requests since 0 days, 00:09:59 hours
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS] Requests: 0.10/min last, 0.10/min overall, 0.10/min top
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS] Dnsstats: 0.00/min last, 0.00/min overall, 0.00/min top
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS] Hitrates: 0.0% ruleset, 0.0% parent, 0.0% child, 0.0% rates
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS] Timeouts: 0.0% (0 of 0 dns queries)
    Jun  9 14:24:18 srv1 postfwd2/master[37244]: [STATS]   1 matches for id:  R001
    

    Now, all users sending mail messages via the SMTP server will be set limits corresponding to the rules specified in postfwd (in our case, only one rule is set). However, all messages sent through / usr / sbin / sendmail will not be filtered, because Postfix sends them directly to the queue, bypassing postfwd.

    Configuring a stub for / usr / sbin / sendmail


    The simplest and most effective solution is to use a “stub” for the script / usr / sbin / sendmail, which would send all correspondence through an SMTP server with authorization. Accordingly, it is also necessary to prohibit sending messages via SMTP to unauthorized local users. We check that the permit_mynetworks parameter in smtpd_recipient_restrictions is missing in the /etc/postfix/main.cf configuration file - in this case, even local users will need authorization.
    In order to put the stub solution into practice, you need to create mailboxes on the mail server that will correspond to users, as well as a default box, for example:

    • site1@myserver.org
    • site2@myserver.org
    • default@myserver.org

    You must set one password for all mailboxes (site1, site2, etc.), and another password for default@myserver.org. You do not need to save letters to these mailboxes, so you can configure forwarding in / dev / null.
    A similar configuration for the postfwd rule above allows sending up to 50 letters per hour to all users with a “separate” account, and sending 50 messages “for all” to those users who do not have a separate account.
    The stub will work as follows:

    1. Determine the name of the user who ran our script
    2. Attempt to log in to the mail server using the username obtained in step 1
    3. In case of error authorize as default
    4. Forward email

    The code is written in Perl and requires the installation of additional modules, install them through CPAN:

    srv1:~# cpan Net::SMTP_auth Email::Address
    

    Create a directory in which we will store the script (for example, / usr / local / bin / private), as well as an unprivileged user who will become the "owner" of the directory and script:

    srv1:~# mkdir /usr/local/bin/private
    srv1:~# useradd sendmail
    

    Add our “stub” script to the created directory (/usr/local/bin/private/sendmail.pl), replacing the variables $ smtp_password, $ smtp_default_password and $ server, respectively, with the password of user mailboxes, the password of the mailbox “default” and the address of your host on which the boxes are created:

    #!/usr/bin/perl
    use strict;
    use warnings;
    use Net::SMTP_auth;
    use Email::Address;
    my $user = getpwuid( $< );
    my $smtp_password = 'password';
    my $smtp_default_password = 'password';
    my $server = 'srv1.re-hash.org';
    my $input = '';
    my $to_string = '';
    foreach my $line (  ) {
      $input .= $line;
      if ($line =~ /^To:/) {
        $to_string = $line;
      }
    }
    my @addrs = Email::Address->parse($to_string);
    if (0+@addrs eq 0) {
      die "No recipients";
    }
    my $rec = $addrs[0];
    $rec =~ s/\@/\\@/;
    my $smtp = Net::SMTP_auth->new('127.0.0.1', Port => 25, Timeout => 10, Debug => 0);
    die "Could not connect to SMTP server!\n" unless $smtp;
    if (!$smtp->auth('PLAIN', $user.'@'.$server, $smtp_password)) {
     $smtp->auth('PLAIN', 'default@'.$server, $smtp_default_password) or die "Auth failed!\n";
    }
    $smtp->mail($user.'\@'.$server);
    $smtp->to($rec);
    $smtp->data();
    $smtp->datasend($input);
    $smtp->dataend();
    $smtp->quit;
    

    Set the rights to the directory and script. It is necessary to make sure that users can execute the script, but not read it:

    srv1:~# chown -R sendmail:sendmail /usr/local/bin/private
    srv1:~# chmod -R 4711 /usr/local/bin/private
    

    The old file / usr / sbin / sendmail can be renamed (for example, sendmail-postfix), or you can remove the execution rights from it.
    In principle, now instead of / usr / sbin / sendmail you can use the path /usr/local/bin/private/sendmail.pl (or immediately save the script as / usr / sbin / sendmail), but I wanted to do it differently. So I decided to write another wrapper (yes, a wrapper on top of the wrapper), which will replace / usr / sbin / sendmail. The wrapper code (wrapper.c) is written in C and looks like this:

    /*  wrapper.c  */
    #define REAL_PATH "/usr/local/bin/private/sendmail.pl"
          main(ac, av)
              char **av;
          {
              execv(REAL_PATH, av);
          }
    

    Compile this file, copy it to / usr / sbin and set the appropriate rights:

    srv1:~# cc -o sendmail wrapper.c 
    srv1:~# cp -a ./sendmail /usr/sbin/sendmail
    srv1:~# chown -R sendmail:sendmail /usr/sbin/sendmail
    srv1:~# chmod -R 4711 /usr/sbin/sendmail
    

    After performing the above steps, all sites should send mail messages in the same way as before, but the limits and rules set in the postfwd configuration will begin to apply to sent messages.

    Wrapper source code is available on GitHub: github.com/xtremespb/sendmail-wrapper .
    I will be glad to comments and suggestions on improving the limiting mechanism in the comments.

    Also popular now: