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 the postfwd package using apt:
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):
You can read more about postfwd configuration in the documentation on the project website .
Edit the file / etc / default / postfwd :
Restart the postfwd service:
And check that everything works as it should:
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:
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:
After the reboot, postfwd starts its work and periodically displays statistics in the mail log (/var/log/mail.log):
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.
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:
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:
The code is written in Perl and requires the installation of additional modules, install them through CPAN:
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:
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:
Set the rights to the directory and script. It is necessary to make sure that users can execute the script, but not read it:
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:
Compile this file, copy it to / usr / sbin and set the appropriate rights:
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.
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:
- Determine the name of the user who ran our script
- Attempt to log in to the mail server using the username obtained in step 1
- In case of error authorize as default
- 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.