ユーザ用ツール

サイト用ツール


サイドバー

Index

はじめてのおつかい






DokuWiki整形記法


PlayGround



serverapps:nginx:mailproxy

mail_moduleによるMail Proxy

このページについて

Nginxのsmtp proxyは送信用としては利用可能ですが、自ドメイン宛のメール受信には利用できません1)

詳しくはQiitaに書いてみましたNginxでsmtp proxyをしてはいけない

install

Nginxのインストールはこちらを参照ください

なお、以下のオプションをONにしています。

  • MAIL_IMAP
  • MAIL_POP3
  • MAIL_SMTP

/usr/local/etc/nginx/nginx.conf

mail {
    auth_http  authserver/mailauth/auth.php ;
    proxy on; 
    proxy_pass_error_message on;

    #ssl_certificate /usr/local/etc/acme/SSL/fullchain.pem;
    #ssl_certificate_key /usr/local/etc/acme/SSL/privkey.pem;
    #ssl_protocols TLSv1.2;
    #ssl_session_cache shared:MAIL:10m;

    smtp_capabilities PIPELINING 8BITMIME "SIZE 20480000" DSN;
    #pop3_capabilities TOP USER UIDL;
    #imap_capabilities IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=LOGIN;
    smtp_auth plain login;

  # SMTP
  server {
    listen      25;
    protocol    smtp;
    xclient     off;
    auth_http_header PORT 25;

    error_log       /var/log/nginx/mail-error.log;

  }

}

認証: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は認証を経ていなくても、自ドメイン宛のメールについては受け入れるという設定がされています。
しかしながら、今回のケースではNginx経由での接続が適切な認証を経たものかをPostfix側で知ることはできません

よって、前段で書いた通りPostfixはこの別ポートからの接続を認証を経たものとして無条件に受け入れます
受信用ポートを通して接続された場合、Postfixは認証を経たものとして無条件に受け入れるため、結果として自ドメイン以外宛のメールであっても受け入れてしまいます。

オープンリレーの出来上がりです。orz

参考

1)
つか、してはならない
serverapps/nginx/mailproxy.txt · 最終更新: 2023/08/07 13:14 by hayashi