Nginx에서 smtp 프록시를해서는 안됩니다.
결론을 먼저
대규모 부하 분산 등을 하는 경우 이외의 일반적인 메일 서버에 있어서는 힘들어서 이익이 없다. 뒷면에 두고 싶다면 PortNAT 등을 추천합니다.
왜?
Nginx의 smtp proxy는 전송용으로는 이용 가능하지만 메일 수신에는 이용할 수 없다. (라고 하는 것보다, 이용해서는 안 된다. 자 도메인에 대해서도 안 된다!)
그래? 이것으로 끝나면 안돼?
그럼, 순서를 따라 설명합니다.
환경
다음과 같이 연결된 환경을 가정합니다.
OS에는 FreeBSD10.3을 사용하고 있습니다.
Mysql의 테이블 관리에는 PostfixAdmin을 이용하고 있습니다.
Nginx의 smtp auth
Nginx 측 설정
쉽게 nginx.conf를 작성해보십시오.
간단하게하기 위해 smtp 만 ssl없이 작성합니다.
nginx.confmail {
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를 이용한 전송은 가능합니다.
프록시 인증 및 메일 서버
그런데, 전단에서 「Postfix의 설치가 적정하게」라고 썼습니다만, 인증 부분 소스를 보면 알겠습니다만, 인증시에 Nginx 전해지는 것은 인증의 성부와 메일 서버의 주소, 포트만이 됩니다.
Nginx는 전해진 메일 서버에 접속해 메일의 송수신을 실시하게 됩니다만, Postfix측은 인증이 적절히 행해지고 있는지 어떤지 모르기 때문에, Nginx로부터의 메일은 모두 인증을 거친 것 로 무조건 수락하도록 설정됩니다.
이메일 수신
메일을 보낼 수 있었으므로 여기에서 수신자를 생각합니다.
당연히 다른 메일 서버는 로그인을 위한 정보를 가지고 있지 않으므로, 다른 포트에서 인증 없이 수신할 수 있도록 설정합니다.
보통의 경우 Postfix는 인증을 거치지 않아도 자기 도메인으로 향하는 메일에 대해서는 받아들이는 설정이 되어 있습니다.
그러나 이번 사례에서는 Nginx를 통한 연결이 적절한 인증을 거쳤는지 Postfix 측에서 알 수 없습니다.
따라서 앞에서 쓴대로 Postfix는 이 다른 포트로부터의 접속을 인증을 거친 것으로서 무조건 받아들입니다.
결과
수신용 포트를 통해 접속된 경우 Postfix는 인증을 거친 것으로 무조건 받아들이므로, 결과적으로 자 도메인 이외에 목적지의 메일이라도 받아들여 버립니다.
오픈 릴레이의 완성입니다. orz
요약
nginx의 smtp 프록시를 실제로 사용하는 것을 생각하면 메일 수신에 대해서는 별도의 조치를 취해야합니다.
ISP처럼 여러 서버에서 부하의 분산을 도모해야 하는 환경이라면 그 노력도 보상받을 것입니다만, 일반적인 서버에서는 이용하지 않는 것이 편하다고 생각합니다.
덤
Nginx에서 Upstream을 사용한 프록시를 어떻게 작성해 보았습니다.
다음과 같이 연결된 환경을 가정합니다.
OS에는 FreeBSD10.3을 사용하고 있습니다.
Mysql의 테이블 관리에는 PostfixAdmin을 이용하고 있습니다.
Nginx의 smtp auth
Nginx 측 설정
쉽게 nginx.conf를 작성해보십시오.
간단하게하기 위해 smtp 만 ssl없이 작성합니다.
nginx.confmail {
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를 이용한 전송은 가능합니다.
프록시 인증 및 메일 서버
그런데, 전단에서 「Postfix의 설치가 적정하게」라고 썼습니다만, 인증 부분 소스를 보면 알겠습니다만, 인증시에 Nginx 전해지는 것은 인증의 성부와 메일 서버의 주소, 포트만이 됩니다.
Nginx는 전해진 메일 서버에 접속해 메일의 송수신을 실시하게 됩니다만, Postfix측은 인증이 적절히 행해지고 있는지 어떤지 모르기 때문에, Nginx로부터의 메일은 모두 인증을 거친 것 로 무조건 수락하도록 설정됩니다.
이메일 수신
메일을 보낼 수 있었으므로 여기에서 수신자를 생각합니다.
당연히 다른 메일 서버는 로그인을 위한 정보를 가지고 있지 않으므로, 다른 포트에서 인증 없이 수신할 수 있도록 설정합니다.
보통의 경우 Postfix는 인증을 거치지 않아도 자기 도메인으로 향하는 메일에 대해서는 받아들이는 설정이 되어 있습니다.
그러나 이번 사례에서는 Nginx를 통한 연결이 적절한 인증을 거쳤는지 Postfix 측에서 알 수 없습니다.
따라서 앞에서 쓴대로 Postfix는 이 다른 포트로부터의 접속을 인증을 거친 것으로서 무조건 받아들입니다.
결과
수신용 포트를 통해 접속된 경우 Postfix는 인증을 거친 것으로 무조건 받아들이므로, 결과적으로 자 도메인 이외에 목적지의 메일이라도 받아들여 버립니다.
오픈 릴레이의 완성입니다. orz
요약
nginx의 smtp 프록시를 실제로 사용하는 것을 생각하면 메일 수신에 대해서는 별도의 조치를 취해야합니다.
ISP처럼 여러 서버에서 부하의 분산을 도모해야 하는 환경이라면 그 노력도 보상받을 것입니다만, 일반적인 서버에서는 이용하지 않는 것이 편하다고 생각합니다.
덤
Nginx에서 Upstream을 사용한 프록시를 어떻게 작성해 보았습니다.
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 ;
<?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();
?>
Postfix 설치가 적절하게 이루어지면 이제 SmtpAuth를 이용한 전송은 가능합니다.
프록시 인증 및 메일 서버
그런데, 전단에서 「Postfix의 설치가 적정하게」라고 썼습니다만, 인증 부분 소스를 보면 알겠습니다만, 인증시에 Nginx 전해지는 것은 인증의 성부와 메일 서버의 주소, 포트만이 됩니다.
Nginx는 전해진 메일 서버에 접속해 메일의 송수신을 실시하게 됩니다만, Postfix측은 인증이 적절히 행해지고 있는지 어떤지 모르기 때문에, Nginx로부터의 메일은 모두 인증을 거친 것 로 무조건 수락하도록 설정됩니다.
이메일 수신
메일을 보낼 수 있었으므로 여기에서 수신자를 생각합니다.
당연히 다른 메일 서버는 로그인을 위한 정보를 가지고 있지 않으므로, 다른 포트에서 인증 없이 수신할 수 있도록 설정합니다.
보통의 경우 Postfix는 인증을 거치지 않아도 자기 도메인으로 향하는 메일에 대해서는 받아들이는 설정이 되어 있습니다.
그러나 이번 사례에서는 Nginx를 통한 연결이 적절한 인증을 거쳤는지 Postfix 측에서 알 수 없습니다.
따라서 앞에서 쓴대로 Postfix는 이 다른 포트로부터의 접속을 인증을 거친 것으로서 무조건 받아들입니다.
결과
수신용 포트를 통해 접속된 경우 Postfix는 인증을 거친 것으로 무조건 받아들이므로, 결과적으로 자 도메인 이외에 목적지의 메일이라도 받아들여 버립니다.
오픈 릴레이의 완성입니다. orz
요약
nginx의 smtp 프록시를 실제로 사용하는 것을 생각하면 메일 수신에 대해서는 별도의 조치를 취해야합니다.
ISP처럼 여러 서버에서 부하의 분산을 도모해야 하는 환경이라면 그 노력도 보상받을 것입니다만, 일반적인 서버에서는 이용하지 않는 것이 편하다고 생각합니다.
덤
Nginx에서 Upstream을 사용한 프록시를 어떻게 작성해 보았습니다.
메일을 보낼 수 있었으므로 여기에서 수신자를 생각합니다.
당연히 다른 메일 서버는 로그인을 위한 정보를 가지고 있지 않으므로, 다른 포트에서 인증 없이 수신할 수 있도록 설정합니다.
보통의 경우 Postfix는 인증을 거치지 않아도 자기 도메인으로 향하는 메일에 대해서는 받아들이는 설정이 되어 있습니다.
그러나 이번 사례에서는 Nginx를 통한 연결이 적절한 인증을 거쳤는지 Postfix 측에서 알 수 없습니다.
따라서 앞에서 쓴대로 Postfix는 이 다른 포트로부터의 접속을 인증을 거친 것으로서 무조건 받아들입니다.
결과
수신용 포트를 통해 접속된 경우 Postfix는 인증을 거친 것으로 무조건 받아들이므로, 결과적으로 자 도메인 이외에 목적지의 메일이라도 받아들여 버립니다.
오픈 릴레이의 완성입니다. orz
요약
nginx의 smtp 프록시를 실제로 사용하는 것을 생각하면 메일 수신에 대해서는 별도의 조치를 취해야합니다.
ISP처럼 여러 서버에서 부하의 분산을 도모해야 하는 환경이라면 그 노력도 보상받을 것입니다만, 일반적인 서버에서는 이용하지 않는 것이 편하다고 생각합니다.
덤
Nginx에서 Upstream을 사용한 프록시를 어떻게 작성해 보았습니다.
nginx의 smtp 프록시를 실제로 사용하는 것을 생각하면 메일 수신에 대해서는 별도의 조치를 취해야합니다.
ISP처럼 여러 서버에서 부하의 분산을 도모해야 하는 환경이라면 그 노력도 보상받을 것입니다만, 일반적인 서버에서는 이용하지 않는 것이 편하다고 생각합니다.
덤
Nginx에서 Upstream을 사용한 프록시를 어떻게 작성해 보았습니다.
손잡이로 SQL 생성하고 있습니다. 이대로 사용하면 안돼. ↩
Reference
이 문제에 관하여(Nginx에서 smtp 프록시를해서는 안됩니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/Toshinori_Hayashi/items/825248e5d4641fb5e8c7텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)