本文介绍基于nginx的邮局反向代理配置方案。nginx对来源于客户端的pop3/smtp/imap请求予以转发到后端postfix,后端邮件服务器采用postfix 2.8.0,已配置并正常运行。
本方案参考 Using a php script on apache server as the auth backend ,并基于此方案进行改进,并增加了对smtp的代理。
环境配置:centos 5.5 + nginx 1.0.4
软件安装:
yum groupinstall 'Development Tools' -y
rpm -Uhv http://apt.sw.be/redhat/el5/en/i386/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.i386.rpm
yum install libxml2-devel libxslt-devel pcre-devel libtool-ltdl libtool-ltdl-devel
cd /usr/src
wget http://nginx.org/download/nginx-1.0.4.tar.gz
tar -zxf nginx-1.0.4.tar.gz
cd nginx-1.0.4
./configure –prefix=/usr/local/nginx –with-mail –without-http
make && make install
配置nginx.conf:
#user nobody;
worker_processes 1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
mail {
auth_http 指定IP:80/auth.php;
pop3_capabilities "TOP" "USER";
imap_capabilities "IMAP4rev1" "UIDPLUS";
server {
listen 110;
protocol pop3;
proxy on;
}
server {
listen 143;
protocol imap;
proxy on;
}
server {
listen 25;
protocol smtp;
proxy on;
smtp_auth login plain;
xclient off;
}
}
说明:
1.安装nginx时禁掉了http(–without-http),因为我们的目标只是转发pop3/smtp/imap请求,故nginx.conf也是相当简单,只有mail模块。如果还需要代理80端口(例如webmail),可以自行编译对http的支持。
2.smtp的配置模块里必须加入xclient off,否则当nginx向后转发smtp请求时,postfix将报“lost connection after XCLIENT”,同时nginx报“550 5.7.0 Error: insufficient authorization”. nginx对smtp的代理,与pop3/imap是不同的,详细见后文。
3.指定IP是用于认证的,需要放认证脚本auth.php. 认证脚本的作用就是验证用户和密码,一般自定义,可以放在任意的服务器上。本方案中选择放在后端邮件服务器上,便于管理。
这里有一个问题,postfix本身已经集成了认证机制(本人采用的是cyrus sasl2+courier-authlib),为什么加了反向代理,认证过程就要移动到反向代理上呢?这样岂不是就变成非透明代理了吗?为什么不作纯碎的透明代理呢?
根据测试,如果这个认证脚本不设验证,直接透传所有pop3/imap请求到后端,在后端邮件服务器还会进行一次认证,但是对于smtp请求,将不再认证,而直接按照转发规则进行转发(因为反向代理的ip加到了postfix的mynetworks中,见后文)。这两种不同的差异应该是跟协议有关。
为了保持统一,在本文的方案中,auth.php集成了pop3/imap/smtp的三种认证。这样的功能架构类似于游戏服务器的,登录服务器和游戏服务器是分开的。
4.在邮件服务器postfix/etc/main.cf中,修改mynetworks值,加入本反向代理的ip,并重载postfix:postfix -s reload
关于xclient:xclient的作用,是将前端的服务器模拟作为一个邮件客户端,而向后端的postfix进行认证和执行发送,但是postfix还需要一个打patch才能完美支持xclient。
关于此问题的讨论可以参见 http://forum.nginx.org/read.php?2,173197,173246#msg-173246
auth.php:
<?php
/**
* @see xiabaibai.net
*/
if(!isset($_SERVER ["HTTP_AUTH_USER"] ) || ! isset($_SERVER ["HTTP_AUTH_PASS"] )) {
fail(0);
}
$username = $_SERVER ["HTTP_AUTH_USER"];
$userpass = $_SERVER ["HTTP_AUTH_PASS"];
$protocol = $_SERVER ["HTTP_AUTH_PROTOCOL"];
$backend_port = 110;
if($protocol == "imap") {
$backend_port = 143;
} elseif ($protocol == "smtp") {
$backend_port = 25;
}
list($uid, $domain) = explode("@", $username);
$auth = authuser($username, $userpass);
if(!$auth) fail (-2);
pass($_SERVER["SERVER_ADDR"], $backend_port);
//自定义认证,sql查询或者api
function authuser($user, $pass) {
return true;
}
function fail($code) {
switch($code){
case 0: header("Auth-Status: Parameter lost"); break;
case -1: header("Auth-Status: No Back-end Server"); break;
case -2: header("Auth-Status: Invalid login or password" ); break;
}
exit();
}
function pass($server, $port) {
header("Auth-Status: OK" );
header("Auth-Server: $server" );
header("Auth-Port: $port" );
exit();
}
?>