Nginx as an IMAP/POP3 proxy Part 2
November 11 2011 11:11 CET
A month or so ago I started discovering some problems with my previous implementation.
It seems that either my Perl script or the nginx embedded Perl module suffers from memory leaks.
Now, the easy way to fix this would be to run a /etc/init.d/nginx restart every so often. However, that would of course suck.
So I started looking into alternative ways, using FastCGI to serve the authentication script.
The normal fcgiwrapper in Debian was way to slow though. Handling only about 30 requests/sec.
Enter FCGI-Daemon by Dmitry Smirnov. It works by keeping the processes alive not respawning Perl on every request.
With this I was able to achieve 2500-3000 request/sec. More than enough to handle IMAP/POP3 authentications.
I’ve included an updated authentication script for use with this.
auth.pl
#!/usr/bin/perl
use Digest::HMAC_MD5 qw/ hmac_md5_hex /;
use DBI;
use URI::Escape;
use CGI;
print "Content-type: text/html\n";
my $q = CGI->new;
my $auth_shared_secret = $q->http("X-NGX-Auth-Key");
# Shared secret to ensure that the request comes from nginx
if ( $auth_shared_secret ne "your secret" ) {
print "Auth-Status: Authentication failed.\n\n";
print STDERR "Wrong X-NGC-Auth-Key $auth_shared_secret";
exit(0);
}
my $dsn = "DBI:mysql:database=postfix;host=1.2.3.4";
our $dbh =
DBI->connect_cached( $dsn, 'mailproxy', 'p@ssw0rd',
{ AutoCommit => 1, mysql_auto_reconnect => 1 } );
our $auth_ok;
our $protocol_ports = {};
$protocol_ports->{'pop3'} = 110;
$protocol_ports->{'imap'} = 143;
$protocol_ports->{'smtp'} = 25;
if ( !defined $dbh || !$dbh->ping() ) {
( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
localtime(time);
$dbh =
DBI->connect_cached( $dsn, 'mailproxy', 'p@ssw0rd',
{ AutoCommit => 1, mysql_auto_reconnect => 1 } );
printf STDERR
"%4d/%02d/%02d %02d:%02d:%02d [notice] : MySQL server connection lost. Reconnecting.\n",
$year + 1900, $mon + 1, $mday, $hour, $min, $sec;
}
my $auth_method = $q->http("Auth-Method");
my $username = uri_unescape( $q->http("Auth-User") );
my $password = uri_unescape( $q->http("Auth-Pass") );
my $salt = $q->http("Auth-Salt");
our $sth = $dbh->prepare("select clear from users where email=? limit 1");
$sth->execute($username);
my $hash = $sth->fetchrow_hashref();
my $real_password = $hash->{'clear'};
# Authorize user
if (
( $auth_method eq "plain" && $password eq $real_password )
or ( $auth_method eq "cram-md5"
&& $password eq hmac_md5_hex( $salt, $real_password ) )
)
{
# Auth OK, find mail server
our $sth = $dbh->prepare(
"select destination_mailstore from transport where domain=? limit 1");
my $domain = $q->http("Auth-User");
# remove @ and everything before
$domain =~ s/^.*@//;
$sth->execute($domain);
my $hash = $sth->fetchrow_hashref();
my $mailserver = $hash->{'destination_mailstore'};
$mailserver =~ s/smtp://;
print "Auth-User: $username\n";
print "Auth-Pass: $real_password\n";
print "Auth-Status: OK\n";
print "Auth-Server: $mailserver\n";
$auth_port = $protocol_ports->{ $q->http("Auth-Protocol") };
print "Auth-Port: $auth_port\n";
}
else {
print "Auth-Status: Authentication failed.\n";
}
print "\n";
I’ve also made a GIST of this here.