Slide 1

Slide 1 text

Net::SMTP Net::SMTP Nagano.rb #6 2020-11-28 とみたまさひろ 1

Slide 2

Slide 2 text

自己紹介 自己紹介 とみたまさひろ ←投げ銭はこちら MySQL / メール / 文字化け 富士通クラウドテクノロジーズ勤務 https://twitter.com/tmtms https://tmtms.hatenablog.com https://zenn.dev/tmtms 2

Slide 3

Slide 3 text

SMTP SMTP Simple Mail Transfer Protocol (1982) → (2001) → (2008) メールメッセージ形式は → → ポート番号は 25 (smtp) RFC 821 2821 5321 RFC 822 2822 5322 3

Slide 4

Slide 4 text

% nc smtp.example.com 25 ← 220 smtp.example.com ESMTP Postfix → EHLO client.example.net ← 250-smtp.example.com 250-PIPELINING 250-SIZE 102400000 250-VRFY 250-ETRN 250-STARTTLS 250-AUTH DIGEST-MD5 NTLM CRAM-MD5 PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250 SMTPUTF8 4

Slide 5

Slide 5 text

→ MAIL FROM: ← 250 2.1.0 Ok → RCPT TO: ← 250 2.1.5 Ok → RCPT TO: ← 250 2.1.5 Ok → DATA ← 354 End data with . → From: [email protected] To: [email protected] Cc: [email protected] Subject: test message body . ← 250 2.0.0 Ok: queued as F074F9FB0E → QUIT ← 221 2.0.0 Bye 5

Slide 6

Slide 6 text

古き良き時代 6

Slide 7

Slide 7 text

迷惑メール 7

Slide 8

Slide 8 text

不正中継防止 不正中継防止 8

Slide 9

Slide 9 text

直接迷惑メールを送りつけてくるやつ対策 直接迷惑メールを送りつけてくるやつ対策 Outbound Port 25 Blocking (OP25B) 接続元のプロバイダーが外向けの25ポートをブロック プロバイダーが用意したメールサーバー経由でしか 外にメールを送れない 受信と送信(中継)の分離 9

Slide 10

Slide 10 text

受信(MX) 受信(MX) TCP 25番ポート 自分宛のものしか受け付けない 一般ユーザーからは来ない 怪しいクライアントは拒否(設定次第) 送信者ドメインのSPFに登録されているか IPアドレスを逆引き&正引きして 元のIPアドレスになるか EHLO名がDNS上に存在しているか 10

Slide 11

Slide 11 text

送信(中継) 送信(中継) TCP 587番ポート(submission) TCP 465番ポート(smtps, submissions) どこ宛でもOK 信頼できるクライアントからしか受け付けない ローカルネットワークのクライアント 認証が通ったクライアント 11

Slide 12

Slide 12 text

SMTP認証 SMTP認証 メール送信時に認証が必要な場合 PLAIN はユーザー名とパスワードを Base64 しただけの平文 CRAM-MD5 等を使えば Challenge-Response 方式の暗号化もできるけど サーバー内に平文のパスワードを保持しておかないといけないのがイマイチ → AUTH PLAIN dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= ← 235 2.7.0 Authentication successful 12

Slide 13

Slide 13 text

通信暗号化(STARTTLS) 通信暗号化(STARTTLS) SMTP 接続後に STARTTLS 命令を発行すると それ以降 TLS での暗号化通信になる (465番ポートは接続時からTLS) ← 220 smtp.example.com ESMTP Postfix → EHLO client.example.net ← 250-smtp.example.com 250-STARTTLS ... → STARTTLS ← 220 2.0.0 Ready to start TLS --- ここから TLS 通信 --- → EHLO client.example.net ← 250-smtp.example.com ... → AUTH PLAIN dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= ← 235 2.7.0 Authentication successful 13

Slide 14

Slide 14 text

暗号化通信は手動ではできないので openssl を使う % openssl s_client -connect smtp.example.com:587 -starttls smtp ...STARTTLS まで自動でやってくれる... --- ここから TLS 通信 --- → EHLO client.example.net ← 250-smtp.example.com ... 14

Slide 15

Slide 15 text

TLS証明書の検証 TLS証明書の検証 オレオレ証明書とか期限切れ証明書はエラーにしたい % openssl s_client -connect smtp.example.com:587 -starttls smtp -verify_return_error ... Verification error: certificate has expired --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 10 (certificate has expired) --- % 15

Slide 16

Slide 16 text

証明書ホスト名の検証 証明書ホスト名の検証 証明書が正しくても自分がアクセスしてるサーバー用の 証明書じゃないかもしれない 接続先に指定した文字列とは異なるサーバー名を 使いたいこともある(テストとかで) % openssl s_client -connect smtp.example.com:587 -starttls smtp -verify_return_error -verify_hostname smtp.example.com % openssl s_client -connect 192.168.11.22:587 -starttls smtp -verify_return_error -verify_hostname smtp.example.com 16

Slide 17

Slide 17 text

ここから Ruby の話 ここから Ruby の話 17

Slide 18

Slide 18 text

Net::SMTP Net::SMTP めっちゃ簡単 require 'net/smtp' Net::SMTP.start('smtp.example.com', 25) do |smtp| smtp.send_message(<

Slide 19

Slide 19 text

SMTP認証 SMTP認証 ユーザー名とパスワードを指定したいだけなのに EHLO 名を書かないといけないのがイマイチ 認証が必要なのは送信サーバーに送信するとき その場合は EHLO 名は重要ではないはず デフォルトだとTLSじゃないんだけど デフォルトの認証方式は PLAIN = 平文 Net::SMTP.start('smtp.example.com', 587, 'client.example.net', 'username', 'password') do |smtp| ... end 19

Slide 20

Slide 20 text

STARTTLS STARTTLS Net::SMTP は start よりも前に enabel_starttls が必要なのがイマイチ smtp = Net::SMTP.new('smtp.example.com', 587) smtp.enable_starttls smtp.start('client.example.com', 'username', 'password') do ... end 20

Slide 21

Slide 21 text

TLS(465番ポート) TLS(465番ポート) enabel_starttls じゃなくて enable_tls enable_starttls と enable_tls 両方指定するとエラー smtp = Net::SMTP.new('smtp.example.com', 465) smtp.enable_tls smtp.start('client.example.com', 'username', 'password') do ... end 21

Slide 22

Slide 22 text

証明書の検証 証明書の検証 デフォルトでは検証しない かなりダメな感じになってきた… smtp = Net::SMTP.new('smtp.example.com', 587) context = OpenSSL::SSL::SSLContext.new context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER) smtp.enable_starttls(context) smtp.start('client.example.com', 'username', 'password') do ... end 22

Slide 23

Slide 23 text

証明書ホスト名の検証 証明書ホスト名の検証 デフォルトでは証明書の検証をしないのに なぜかホスト名の検証だけはしてる(バグっぽい) Net::SMTP は常に #start() の第1引数の文字列を使うので別のサーバ ー名を使うことはできない smtp = Net::SMTP.new('192.168.11.22', 587) context = OpenSSL::SSL::SSLContext.new context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER) smtp.enable_starttls(context) smtp.start('client.example.com', 'username', 'password') #=> hostname "192.168.11.22" does not match the server # certificate (OpenSSL::SSL::SSLError) 23

Slide 24

Slide 24 text

ということで の コミット権をもらった Net::SMTP は Ruby 2.7 から Gem になってる https://github.com/ruby/net-smtp/ 24

Slide 25

Slide 25 text

キーワード引数化 キーワード引数化 ↓ EHLO名を指定しなくても認証情報を指定可能 Net::SMTP.start(hostname, port, helo_name, username, password, authtype) Net::SMTP.start(hostname, port, helo: helo_name, user: username, password: password, authtype: authtype) 25

Slide 26

Slide 26 text

デフォルトで STARTTLS を使用 デフォルトで STARTTLS を使用 非互換! 非互換! サーバーが対応していれば自動的に STARTTLS を使用 Net::SMTP.start(hostname, port) { ... } → EHLO client.example.net ← 250-smtp.example.com 250-PIPELINING 250-SIZE 102400000 250-VRFY 250-ETRN 250-STARTTLS 250-AUTH DIGEST-MD5 NTLM CRAM-MD5 PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250 SMTPUTF8 26

Slide 27

Slide 27 text

STARTTLS を使用したくない場合はちょっと面倒 smtp = Net::SMTP.new(hostname, port) smtp.disable_starttls smtp.start { ... } 27

Slide 28

Slide 28 text

デフォルトでTLS証明書を検証 デフォルトでTLS証明書を検証 非互換! 非互換! 検証したくない場合(テストとか)は tls_verify: false を指定 Net::SMTP.start(hostname, port, tls_verify: false) { ... } 28

Slide 29

Slide 29 text

デフォルトでホスト名を検証 デフォルトでホスト名を検証 これは今までどおり 証明書を検証しない時はホスト名も検証しない(バグ修正) 接続用の名前と異なるホスト名で検証したい場合は tls_hostname で指定 Net::SMTP.start('192.168.11.22', 587, tls_hostname: 'smtp.example.com') { ... } 29

Slide 30

Slide 30 text

net-smtp gem net-smtp gem Ruby 2.7 の人はこれで使える 正当な TLS 証明書を持ってないサーバーへの接続が エラーになる可能性あり オレオレ証明書とか % gem install net-smtp 30

Slide 31

Slide 31 text

順調にいけば Ruby 3.0 に入るかな 順調にいけば Ruby 3.0 に入るかな 31

Slide 32

Slide 32 text

おわり おわり 32