nginxでメールサービス(SMTP,POP,IMAP)をproxyする

nginxにはメールサービス(SMTP,POP,IMAP)をproxyする機能があるので試してみた。

##1. テスト構成
|サービス|サービス用IPとPort|Proxy先IPとPort|Proxy先ソフトウェア|
|---|---|---|---|
|SMTP|192.168.1.1:587|127.0.0.1:587|Postfix(OUT-BOUND用)|
|SMTPS|192.168.1.1:465|127.0.0.1:587|Postfix(OUT-BOUND用)|
|POP|192.168.1.1:110|127.0.0.1:110|dovecot|
|POPS|192.168.1.1:995|127.0.0.1:110|dovecot|
|IMAP|192.168.1.1:143|127.0.0.1:143|dovecot|
|IMAPS|192.168.1.1:993|127.0.0.1:143|dovecot|

##2. nginx.confにproxy設定の追加

```lang:nginx.conf
mail {
  auth_http  127.0.0.1/php/proxy.php ;
  proxy on; 
  proxy_pass_error_message on;

  ssl_certificate /etc/pki/tls/certs/startssl.crt;
  ssl_certificate_key /etc/pki/tls/certs/startssl.key;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_session_cache shared:MAIL:10m;

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

  server {
    listen     192.168.1.1:587;
    protocol   smtp;
    starttls   on;
    xclient    on;
    resolver   127.0.0.1 valid=30s;
    resolver_timeout 10s;
    auth_http_header PORT 587;
  }
  server {
    listen     192.168.1.1:465;
    protocol   smtp;
    ssl        on;
    xclient    on;
    resolver   127.0.0.1 valid=30s;
    resolver_timeout 10s;
    auth_http_header PORT 465;
  }
  server {
    listen     192.168.1.1:110;
    protocol   pop3;
    starttls   off;
    auth_http_header PORT 110;
  }
  server {
    listen     192.168.1.1:995;
    protocol   pop3;
    ssl        on;
    auth_http_header PORT 995;
  }
  server {
    listen     192.168.1.1:143;
    protocol   imap;
    starttls   off;
    auth_http_header PORT 143;
  }
  server {
    listen     192.168.1.1:993;
    protocol   imap;
    ssl        on;
    auth_http_header PORT 993;
  }
}
```

##3. Proxy先応答スクリプトの用意
nginxで指定したauth_httpのURLにproxy先を応答するスクリプトを設置する。
Auth-Status, Auth-Server, Auth-Port を応答すればいい。

下記のProxy先応答スクリプトは
- proxy先を 127.0.0.1 固定
- パスワードを test で固定
しています。

```lang:proxy.php
<?php

// set $env from nginx
$env['user']    = getenv('HTTP_AUTH_USER');
$env['passwd']  = getenv('HTTP_AUTH_PASS');
$env['proto']   = getenv('HTTP_AUTH_PROTOCOL');
$env['client']  = getenv('HTTP_CLIENT_IP');
$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",
);

$passwd = urldecode($env['passwd']) ;
$proxyhost = "127.0.0.1" ;
$proxyport = $portmap[$env['proto']];

// maillog
$log = sprintf('user=%s, client=%s, proto=%s', $env['user'], $env['client'], $protomap[$env['port']]);

if ( $passwd === 'test' ) {
   $log = sprintf('proxy=successful, %s, connect=%s:%s', $log, $proxyhost, $proxyport);
   header('Content-type: text/html');
   header('Auth-Status: OK') ;
   header("Auth-Server: 127.0.0.1") ;
   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("ngproxy", LOG_PID , LOG_MAIL);
syslog(LOG_INFO,"$log");
closelog();

?>
```
##4. テスト時の出力

POP Proxy成功時

```lang:maillog
Jan 17 21:14:09 ma3ki ngproxy[2729]: proxy=successful, user=test@example.com, client=192.168.1.2, proto=pop, connect=127.0.0.1:110
Jan 17 21:14:09 ma3ki dovecot: pop3-login: Login: user=<test@example.com>, method=PLAIN, rip=127.0.0.1:41530, lip=127.0.0.1:110, mpid=18457, secured
```

POP Proxy失敗時

```lang:maillog
Jan 17 21:17:24 ma3ki ngproxy[2730]: proxy=failure, user=test@example.com, client=192.168.1.2, proto=pop, passwd=hoge
```

SMTPS Proxy成功時

```lang:maillog
Jan 17 21:22:57 ma3ki ngproxy[2727]: proxy=successful, user=test@example.com, client=192.168.1.2, proto=smtps, connect=127.0.0.1:587
Jan 17 21:22:57 ma3ki postfix/smtpd[7847]: connect from localhost[127.0.0.1]
Jan 17 21:22:57 ma3ki postfix/smtpd[7847]: E1B091044307E: client=[192.168.1.2], sasl_method=XCLIENT, sasl_username=test@example.com
```

##5. 利点
**認証スクリプトを自作することで、柔軟なバックエンドの構成がとれる**

例えば
a. Proxy先をldapやmysql等のdatabaseで管理できる
b. nginxはXCLIENTに対応している為、proxyをしてもpostfixの制限機能が使用できる
c. rbldnsやmemcachedなどのKVSと組み合わせれば、ratelimitが実装できる
d. 認証エラー時のパスワードをログに出力し、ボットが入力するパスワードの調査などができる