LoginSignup
7
6

More than 5 years have passed since last update.

Nginxでsmtp proxyをしてはいけない

Last updated at Posted at 2017-07-14

結論を先に

大規模な負荷分散などをする場合以外の一般的なメールサーバにおいては、労多くして益少なし。裏側におきたいのなら、PortNATなどをお勧めします。

なぜ?

Nginxのsmtp proxyは送信用としては利用可能だが、メール受信には利用できない。(というより、利用すべきでない。自ドメイン宛でもダメ!)

え?これで終わっちゃダメ?
では、順を追って説明します。

環境

以下のように接続されている環境を想定しています。

image.png

OSにはFreeBSD10.3を使用しています。
Mysqlのテーブル管理にはPostfixAdminを利用しています。

Nginxのsmtp auth

Nginx側の設定

簡単にnginx.confを書いてみます。
シンプルにするためsmtpのみ、sslなしで記述しています。

nginx.conf
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

auth.php
<?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をいかに記述してみました。


  1. 手抜きでSQL生成してます。このまま使っちゃダメだよ。 

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6