Postfix で SMTP 認証するには、Cyrus SASL と Dovecot SASL のどちらかを使用できる。 Cyrus SASL は、Postfix のビルド時に SASL ライブラリを組み込む必要があるが、Dovecot SASL は特殊なライブラリは必要ない。 Dovecot SASL はネットワーク通信で、通信相手として Dovecot サーバーが必要なのだが、シンプルなテキストベースのプロトコルなので、簡単に独自の認証プログラムを作ることもできる。
Dovecot 認証プロトコルについての詳細は https://doc.dovecot.org/developer_manual/design/auth_protocol/ に説明があるが、Postfix に対応するだけであれば、完全な実装は必要ない。
以下、Postfix がしゃべる Dovecot 認証プロトコルの説明。
プロトコル
接続
以下、クライアント(C)は Postfix(smtpd)、サーバー(S)は認証プログラム。
smtpd 起動時:
C: VERSION<tab>1<tab>0 C: CPID<tab>$pid
$pid
: Postfix のプロセス番号。
応答
S: VERSION<tab>1<tab>$minor S: MECH<tab>$name<tab>$params S: SPID<tab>$pid S: DONE
$minor
- プロトコルのマイナーバージョン。無視されるので数値であればなんでもいい。なおメジャーバージョンは
1
でないと Postfix がエラーになる。
- プロトコルのマイナーバージョン。無視されるので数値であればなんでもいい。なおメジャーバージョンは
$name
- SASL mechanism 名。
PLAIN
やLOGIN
やCRAM-MD5
等。
- SASL mechanism 名。
$params
- SASL mechanism の属性。有効な値は
anonymous
,plaintext
,dictionary
,active
,forward-secrecy
,mutual-auth
。private
は無視される。タブ区切りで複数指定可。 これは Postfix のsmtpd_sasl_tls_security_options
の値と関連する。たとえば、smtpd_sasl_tls_security_options=noplaintext
の場合、plaintext
属性の mechanism は Postfix は使用しない(EHLO の応答にもでてこない)。
- SASL mechanism の属性。有効な値は
$pid
- サーバープロセスのプロセス番号。無視される。
MECH
は複数行可。MECH
で返した名前が EHLO
の応答の AUTH
に現れる。
SPID
は Postfix には無視されるだけなのでなくてもよい。けど、MECH
よりも前に返すとエラーになる(これはプロトコルに合ってないような気がする…)。
認証
SMTP の AUTH 命令時:
C: AUTH<tab>$id<tab>$mech<tab>service=$service<tab>$params
$id
: 接続識別子。AUTH 命令の度にインクリメントされる。応答の $id と一致しなければエラー。$mech
: SASL mechanism 名。$service
: Postfix のsmtpd_sasl_service
の値。
Postfix から渡される $params
は次の通り:
nologin
- なにこれ?謎…。
lip=$localip
- SMTP 接続のサーバー側IPアドレス。
rip=$remoteip
- SMTP 接続のクライアント側IPアドレス。
secured
- SMTP 接続が TLS の場合に付加。
resp=$resp
- SMTP AUTH 命令に引数があれば付加。たとえば
AUTH PLAIN xxxx
の場合はxxxx
の部分。
- SMTP AUTH 命令に引数があれば付加。たとえば
応答
認証成功時:
S: OK<tab>$id<tab>user=$userid
$userid
: ユーザー名
認証失敗時:
S: FAIL<tab>$id<tab>reason=$reason
$reason
: 認証失敗の理由
認証のために続きのデータが必要な場合:
S: CONT<tab>$id<tab>$data
$data
SMTP クライアントに334
で送られるデータ。LOGIN
認証時のプロンプト文字列(Username:
)等。
認証の続き
C: CONT<tab>$id<tab>$data
$id
:AUTH
時の$id
と同じもの。$data
: 認証に必要なデータ。
切断
特に命令はない。クライアント(Postfix)からネットワークを切断するだけ。
オレオレ認証
Ruby でオレオレ認証を作ってみる。
ネットワーク通信するデーモンプログラムを作って自前で起動する仕組みを作るのは面倒なので、Postfix に管理をまかせる。 spawn を使うと標準入出力を使って簡単に書ける(inetd みたいな感じ)。
etc/postfix/main.cf
smtpd_sasl_path = private/saslauth
/etc/postfix/master.cf
saslauth unix - n n - - spawn user=daemon argv=/usr/local/bin/saslauth.rb
/usr/local/bin/saslauth.rb
#!/usr/bin/ruby def main gets # VERSION gets # CPID puts "VERSION\t1\t0" puts "MECH\tOREORE\tplaintext" puts "DONE" while line = gets cmd, id, mech, *args = line.chomp.split(/\t/) # AUTH params = args.map{|arg| (arg.split(/=/, 2)+[nil])[0, 2]}.to_h case mech when 'OREORE' oreore(id, params) else puts "FAIL\t#{id}\treason=unknown mechanism" end end end # オレオレ認証 # ユーザー名だけで信じちゃう def oreore(id, params) unless params['resp'] puts "FAIL\t#{id}\treason=parameter required" return end user = params['resp'].unpack1('m') puts "OK\t#{id}\tuser=#{user}" end $stdout.sync = true main
テスト
% nc localhost 25 220 servername.localdomain ESMTP Postfix EHLO client 250-servername.localdomain 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-AUTH OREORE 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250 CHUNKING AUTH OREORE aG9nZWhvZ2U= ← "hogehoge" をBase64化したもの 235 2.7.0 Authentication successful QUIT 221 2.0.0 Bye
うまくいった。