結論を先に
大規模な負荷分散などをする場合以外の一般的なメールサーバにおいては、労多くして益少なし。裏側におきたいのなら、PortNATなどをお勧めします。
なぜ?
Nginxのsmtp proxyは送信用としては利用可能だが、メール受信には利用できない。(というより、利用すべきでない。自ドメイン宛でもダメ!)
え?これで終わっちゃダメ?
では、順を追って説明します。
環境
以下のように接続されている環境を想定しています。
OSにはFreeBSD10.3を使用しています。
Mysqlのテーブル管理にはPostfixAdminを利用しています。
Nginxのsmtp auth
Nginx側の設定
簡単にnginx.confを書いてみます。
シンプルにするためsmtpのみ、sslなしで記述しています。
mail {
auth_http authserver/mailauth/auth.php ;
proxy on;
proxy_pass_error_message on;
smtp_capabilities PIPELINING 8BITMIME "SIZE 20480000" DSN;
smtp_auth plain login;
# SMTP
server {
listen 587;
protocol smtp;
xclient off;
auth_http_header PORT 587;
error_log /var/log/nginx/mail-error.log;
}
}
ここできもとなるのは、認証を行うこの部分
auth_http authserver/mailauth/auth.php ;
認証:auth.php
冗長ですが、利用したい方がいるかもしれないので、必要のない部分も載せてあります。1
<?php
// set $env from nginx
$env['user'] = getenv('HTTP_AUTH_USER');
$env['passwd'] = getenv('HTTP_AUTH_PASS');
$env['salt'] = getenv('HTTP_AUTH_SALT');
$env['proto'] = getenv('HTTP_AUTH_PROTOCOL');
$env['method'] = getenv('HTTP_AUTH_METHOD');
$env['attempt'] = getenv('HTTP_AUTH_ATTEMPT');
$env['client'] = getenv('HTTP_CLIENT_IP');
$env['host'] = getenv('HTTP_CLIENT_HOST');
$env['port'] = getenv('HTTP_PORT');
// proxy port map
$portmap = array(
"smtp" => 587,
"pop3" => 110,
"imap" => 143,
);
// protocol map
$protomap = array(
"995" => "pops",
"993" => "imaps",
"110" => "pop",
"143" => "imap",
"587" => "smtp",
"465" => "smtps",
);
// MySQL DB
$dbhost = "DBHOST";
$dbname = "postfix";
$dbuser = "postfix";
$dbpass = "DBPASSW0RD";
$dsn = "mysql:host=$dbhost;dbname=$dbname;charset=utf8";
$user = rtrim($env['user']) ;
$passwd = rtrim($env['passwd']) ;
$proxyhost = getenv('SERVER_ADDR');
$myaddr = getenv('SERVER_ADDR');
$proxyport = $portmap[$env['proto']];
// DB Access
try {
$pdo = new PDO($DSN, $dbuser, $dbpass,array(PDO::ATTR_EMULATE_PREPARES => false));
} catch (PDOException $e) {
$log = 'Dadabase connection failure. '.$e->getMessage();
openlog("nginx-proxy-auth", LOG_PID , LOG_MAIL);
syslog(LOG_INFO,"$log");
closelog();
exit($log);
}
$sql="SELECT password FROM mailbox WHERE username = '$user' AND active=1;";
$stmt = $pdo->query($sql);
$row = $stmt -> fetch();
$hashpass = $row["password"];
$cmd = "/usr/local/bin/doveadm pw -p '$passwd' -t '$hashpass' | /usr/bin/sed -r 's/.*(verified))$/\\1/'";
$result = rtrim(shell_exec($cmd));
$log = sprintf('user=%s, client=%s, proto=%s', $user, $env['client'], $env['proto']);
if ( $result === 'verified' ) {
$log = sprintf('proxy=successful, %s, connect=%s:%s', $log, $proxyhost, $proxyport);
header('Content-type: text/html');
header('Auth-Status: OK') ;
header("Auth-Server: $myaddr") ;
header("Auth-Port: $proxyport") ;
} else {
$log = sprintf('proxy=failure, %s, passwd=%s', $log, $passwd);
header('Content-type: text/html');
header('Auth-Status: Invalid login') ;
}
// write syslog
openlog("nginx-proxy-auth", LOG_PID , LOG_MAIL);
syslog(LOG_INFO,"$log");
closelog();
?>
やっていることはシンプルで、Nginxから引き渡されたユーザ名やPasswordを使用してDBのPassと照合、マッチしていれば'Auth-Status: OK'とメールサーバをかえします。
ここでは、DBですがLDAPなどでも流れは一緒です。
メールの送信
Postfixの設置が適正に行われていれば、これでSmtpAuthを利用した送信はできるはずです。
Proxyの認証とメールサーバ
さて、前段で「Postfixの設置が適正に」と書きましたが、認証部分ソースを見ればわかりますが、認証時にNginx伝えられるのは認証の成否とメールサーバのアドレス、ポートのみとなります。
Nginxは伝えられたメールサーバに接続してメールの送受信を行うことになりますが、Postfix側は認証が適切に行われているかどうかを知りませんので、Nginxからのメールは全て認証を経たものとして無条件に受け入れるよう設定されます。
メールの受信
メールの送信はできましたので、ここで受信側を考えます。
当然ほかのメールサーバはログインのための情報を持っていませんので、別ポートで認証なしで受信できるよう設定します。
通常の場合Postfixは認証を経ていなくても、自ドメイン宛のメールについては受け入れるという設定がされています。
しかしながら、今回のケースではNginx経由での接続が適切な認証を経たものかをPostfix側で知ることはできません。
よって、前段で書いた通りPostfixはこの別ポートからの接続を認証を経たものとして無条件に受け入れます。
結果
受信用ポートを通して接続された場合、Postfixは認証を経たものとして無条件に受け入れるため、結果として自ドメイン以外宛のメールであっても受け入れてしまいます。
オープンリレーの出来上がりです。orz
まとめ
nginxのsmtp proxyを実際に利用することを考えた場合、メール受信については、別途手立てを講じる必要があります。
ISPのように複数のサーバで負荷の分散を図らねばならないような環境でしたら、その労力も報われるでしょうが、一般的なサーバでは利用しない方が楽だと思います。
おまけ
NginxでのUpstreamを使ったproxyをいかに記述してみました。
-
手抜きでSQL生成してます。このまま使っちゃダメだよ。 ↩