Postfixでオレオレ認証を作る
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
うまくいった。
Discussion