Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Net::SMTP

 Net::SMTP

とみたまさひろ

November 28, 2020
Tweet

More Decks by とみたまさひろ

Other Decks in Technology

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  4. % 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

    View full-size slide

  5. → 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

    View full-size slide

  6. 古き良き時代
    6

    View full-size slide

  7. 迷惑メール
    7

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 通信暗号化(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

    View full-size slide

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

    View full-size slide

  15. 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

    View full-size slide

  16. 証明書ホスト名の検証
    証明書ホスト名の検証
    証明書が正しくても自分がアクセスしてるサーバー用の
    証明書じゃないかもしれない
    接続先に指定した文字列とは異なるサーバー名を
    使いたいこともある(テストとかで)
    % 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

    View full-size slide

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

    View full-size slide

  18. Net::SMTP
    Net::SMTP
    めっちゃ簡単
    require 'net/smtp'
    Net::SMTP.start('smtp.example.com', 25) do |smtp|
    smtp.send_message(<'[email protected]', '[email protected]')
    From: [email protected]
    To: [email protected]
    Cc: [email protected]
    Subject: test
    message body
    EOS
    end
    18

    View full-size slide

  19. SMTP認証
    SMTP認証
    ユーザー名とパスワードを指定したいだけなのに
    EHLO 名を書かないといけないのがイマイチ
    認証が必要なのは送信サーバーに送信するとき
    その場合は EHLO 名は重要ではないはず
    デフォルトだとTLSじゃないんだけど
    デフォルトの認証方式は PLAIN = 平文

    Net::SMTP.start('smtp.example.com', 587, 'client.example.net',
    'username', 'password') do |smtp|
    ...
    end
    19

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. 証明書の検証
    証明書の検証
    デフォルトでは検証しない
    かなりダメな感じになってきた…
    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

    View full-size slide

  23. 証明書ホスト名の検証
    証明書ホスト名の検証
    デフォルトでは証明書の検証をしないのに
    なぜかホスト名の検証だけはしてる(バグっぽい)
    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

    View full-size slide

  24. ということで

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

    View full-size slide

  25. キーワード引数化
    キーワード引数化

    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

    View full-size slide

  26. デフォルトで 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  32. おわり
    おわり
    32

    View full-size slide