Ruby Net::SMTP

23588 단어 Rubymailtech

Ruby Net::SMTP


Fujitsu 크라운 기술 회사 Advent Calendar 2020FUJITSU Advent Calendar 2020 5일째 보도다.
회사의 부가 달력이지만 기사 내용은 회사와 상관이 없다.
이것은 nagano.rb #6에 발표된 단락이다.

SMTP


SMTP는 Simple Mail Transfer Protocol의 약칭으로 메일을 보내는 프로토콜입니다.
RFC 변경 사항:

  • RFC 821(1982년)

  • RFC 2821(2001년)

  • RFC 5321(2008년)
  • 메일 메시지 형식(Internet Message Format)의 RFC도 함께 발행되며, SMTP의 다음 번호가 지정됩니다.
  • RFC 822
  • RFC 2822
  • RFC 5322
  • 포트 번호는 25번으로 이름이 배정되었다smtp.
    텍스트 프로토콜이기 때문에 사람도 DATA부터 .까지의 형식은 RFC 5322)이라고 말할 수 있다.
    S: 220 smtp.example.com ESMTP Postfix</span>
    C: EHLO client.example.net</span>
    S: 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
    C: MAIL FROM:<[email protected]>
    S: 250 2.1.0 Ok
    C: RCPT TO:<[email protected]>
    S: 250 2.1.5 Ok
    C: RCPT TO:<[email protected]>
    S: 250 2.1.5 Ok
    C: DATA
    S: 354 End data with <CR><LF>.<CR><LF>
    C: From: [email protected]
       To: [email protected]
       Cc: [email protected]
       Subject: test
       
       message body
       .
    S: 250 2.0.0 Ok: queued as F074F9FB0E
    C: QUIT
    S: 221 2.0.0 Bye
    
    틀린 사람이 많지만 MAIL FROM:< 사이에는 공백이 없다.또한 메일 주소는 <>로 묶어야 한다.
    실제로 많은 서버가 비어 있음<>으로, 없어도 오류가 발생하지 않지만 간혹 프로토콜 위반으로 오류가 발생하는 서버도 있습니다.

    우편물을 수발하다.


    예전에는 메일 서버가 누가 보낸 메일이든 정확한 발송 목적지로 전송되는 것 같았다.

    그러나 발송자가 사칭되거나 스팸메일을 보내는 데 사용된다

    지금
  • 신뢰할 수 있는 고객은 어디든 갈 수 있음
  • 인증 통과 후 어디로든 OK
  • 이외에 본인에게 보내면 OK
  • 이런 설정은 매우 일반적이다.

    하지만 그렇다고 이 서버로 직접 보내는 사람은 대응할 수 없다.
    공급업체 측 대책으로 아웃바운드 포트 25 블록킹(OP25B)을 도입했다.
    공급업체가 외부를 향한 25개 포트를 차단했기 때문에 공급업체가 준비한 메일 서버를 통해서만 외부로 메일을 보낼 수 있다.
    많은 공급업체들이 도입했기 때문에 공급업체가 속한 네트워크에서 외부 메일 서버에 직접 연결할 수 없다.
    그리고 수신과 발송이 분리되었다.

    받기(MX)

  • 자신에게 보내는 메일을 받기 위해
  • TCP 25번 포트(smtp)
  • DNS의 MX 레코드를 통해 지정된 서버
  • 일반 사용자와 연결되지 않음
  • 의심되는 고객 거부(설정에 따라)
  • 발송자 도메인의 SPF에 등록되어 있는지 여부
  • IP 주소를 역라 & 정라로 하여 원래의 IP 주소가 된다
  • EHLO 이름이 DNS에 있는지 여부
  • 전송 (중계)

  • 외부로 보내기 위해
  • TCP 587번 포트(submission)
  • TCP 465번 포트(smtps,submissions)
  • 어디든 갈 수 있어
  • 신뢰할 수 있는 고객의
  • 인증 클라이언트
  • 로컬 네트워크의 클라이언트
  • SMTP 인증


    이메일을 보낼 때 인증이 필요한 경우 SMTP의 AUTH 명령을 사용합니다.
    C: AUTH PLAIN dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ=
    S: 235 2.7.0 Authentication successful
    
    얼핏 알 수 없는 문자열이지만 PLAIN은 기본적으로 64개의 사용자 이름과 비밀번호의 명문일 뿐이다.
    PLAIN 이외의 인증 방식, 예를 들어 CRAM-DMD5 등도 Charlene-Response 방식으로 암호화할 수 있지만 서버 내에 명문으로 된 암호를 유지해야 하는 것은 좋지 않다.
    뭐, 비밀번호만 암호화했어도 메일 본문은 명문이어서 훔쳐볼 수도 있어.

    통신 암호화(STARTTLS)


    그러니까 통신 경로를 암호화해.
    SMTP 연결 후 STARTTLS 명령을 실행하면 TLS의 암호화 통신이 뒤따릅니다.
    또한 465번 포트는 HTTPS와 같고 연결할 때부터 TLS 통신이다.
    S: 220 smtp.example.com ESMTP Postfix
    C: EHLO client.example.net
    S: 250-smtp.example.com
       250-STARTTLS              ← EHLO の応答に STARTTLS が含まれてれば使用可
       ...
    C: STARTTLS
    S: 220 2.0.0 Ready to start TLS
    --- ここから TLS 通信 ---
    C: EHLO client.example.net
    S: 250-smtp.example.com
       ...
    C: AUTH PLAIN dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ=
    S: 235 2.7.0 Authentication successful
    
    암호화 통신은 수동으로 할 수 없으며 SMTP를 손으로 두드리려면openssl을 사용합니다.
    % openssl s_client -connect smtp.example.com:587 -starttls smtp
    (STARTTLS まで自動でやってくれる)
    --- ここから手で入力したものが TLS 通信でサーバーに送られる ---
    C: EHLO client.example.net
    S: 250-smtp.example.com
       ...
    

    TLS 인증서 확인


    기본적으로 오픈스sl은 인증서를 검증하지 않기 때문에 저도 인증서나 기한이 지난 증명서를 통과할 것입니다. 틀리고 싶으면 -verify_return_error 을 추가합니다.
    % 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)
    --- 
    % 
    

    인증서 호스트 이름 확인


    인증서가 합법적일지라도 자신이 방문한 서버용 인증서가 아닐 수도 있다.
    인증서의 호스트 이름을 확인하려면 -verify_hostname를 추가합니다.
    % openssl s_client -connect smtp.example.com:587 -starttls smtp -verify_return_error \
      -verify_hostname smtp.example.com
    

    Ruby SMTP


    다음은 본론.

    Net::SMTP


    Ruby에서 SMTP를 사용할 때net/smtp 프로그램 라이브러리를 사용합니다.아주 간단하게 사용할 수 있어요.
    require 'net/smtp'
    
    Net::SMTP.start('smtp.example.com', 25) do |smtp|
      smtp.send_message(<<EOS, '[email protected]', '[email protected]', '[email protected]')
    From: sender@example.com
    To: rcpt1@example.com
    Cc: rcpt2@example.com
    Subject: test
    
    message body
    EOS
    end
    

    SMTP 인증


    SMTP 인증을 사용할 수도 있습니다.
    Net::SMTP.start('smtp.example.com', 587, 'client.example.net',
                    'username', 'password') do |smtp|
      ...
    end
    
    사용자 이름과 비밀번호만 지정하고 싶은데 EHLO 이름을 써야 하는 것은 좋지 않다.
    인증이 필요한 것은 서버가 메일을 보낼 때이기 때문에 EHLO 이름은 중요하지 않을 것이다.
    기본 인증 방식은 PLAIN, 즉 명문이며 기본적으로 TLS를 사용하지 않습니다.😇

    STARTTLS


    STARTTLS도 간단하게 사용할 수 있지만 Net::SMTP.start 안 돼, 안 Net::SMTP.new 안 되는 부분은 약간 미묘한 느낌이에요.
    smtp = Net::SMTP.new('smtp.example.com', 587)
    smtp.enable_starttls
    smtp.start('client.example.com', 'username', 'password') do
      ...
    end
    
    465번 포트처럼 STARTTLS가 아닌 경우 enable_starttls 대신 TLS를 사용해야 합니다.
    smtp = Net::SMTP.new('smtp.example.com', 465)
    smtp.enable_tls
    smtp.start('client.example.com', 'username', 'password') do
      ...
    end
    
    또한 지정enable_tlsenable_starttls 두 개 모두 오류가 발생할 수 있습니다.

    인증서 검사


    Net::SMTP는 기본적으로 TLS 인증서를 검증하지 않습니다.내 증명서나 기한이 지난 증명서도 통과할 수 있다.
    인증서의 검증은 다음과 같다.
    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
    
    OpenSSL 라이브러리의 사용법을 알아야 하는데 상당히 나빠진 것 같은데...

    인증서 호스트 이름 확인


    Net: SMTP는 기본적으로 인증서를 검증하지 않지만 호스트 이름을 검증하는 이상한 행동을 합니다.
    그리고 항상 enable_tls 또는 .new()의 첫 번째 매개 변수 문자열을 사용하기 때문에 테스트에서 다른 서버 이름을 사용할 수 없습니다.
    IP 주소로 연결하면 호스트 이름이 맞지 않으면 오류가 발생합니다.
    smtp = Net::SMTP.new('192.168.1.2', 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.1.2" does not match the server
    #   certificate (OpenSSL::SSL::SSLError)
    

    Net: SMTP 변경 사항


    그래서 이 안 좋은 부분을 어떻게든 해결하려고 https://github.com/ruby/net-smtp/제출권을 받아서 많이 해봤어요.

    키워드 매개 변수


    매개변수가 키워드 매개변수화되었습니다.
    Net::SMTP.start(hostname, port, helo_name, username, password, authtype)
    

    Net::SMTP.start(hostname, port, helo: helo_name,
                    user: username, password: password, authtype: authtype)
    
    EHLO 이름을 지정하지 않고도 인증 정보를 지정할 수 있습니다.
    Net::SMTP.start('smtp.example.com', 587,
                    user: 'username', password: 'password') do |smtp|
      ...
    end
    

    기본적으로 STARTTLS[호환되지 않음] 사용


    서버가 지원하는 경우 STARTTLS가 자동으로 사용됩니다..start()의 응답EHLO이 있으면 특별히 지정하지 않아도 STARTTLS를 사용합니다.
    Net::SMTP.start(hostname, port) do |smtp|
      ...
    end
    
    하지만 STARTTLS를 사용하지 않으려면 좀 귀찮아요.
    smtp = Net::SMTP.new(hostname, port)
    smtp.disable_starttls
    smtp.start { ... }
    
    테스트 환경과 같은 환경에서 인증서가 제대로 설정되지 않았지만STARTTLS 되돌아오는 환경에서 오류가 발생할 수 있습니다. 이것은 호환되지 않습니다.

    기본 인증 인증서[호환되지 않음]


    인증서도 기본적으로 검증됩니다.이것도 나의 인증서 등을 사용하는 환경에서 발생한 오류이기 때문에 호환되지 않는다.
    인증서를 검증하지 않으려면 키워드 인자EHLO가 추가되었습니다.
    Net::SMTP.start(hostname, port, tls_verify: false) { ... }
    

    호스트 이름 확인


    인증서를 검증하지 않을 때도 호스트 이름을 검증하는 오류 행위를 수정했습니다.STARTTLS시 호스트 이름을 확인하지 않습니다.
    접속에서 사용하는 이름과 다른 호스트 이름tls_verify을 확인하기 위해 키워드 매개 변수가 추가되었습니다.
    이렇게 써도 돼요.
    Net::SMTP.start('192.168.1.2', 587, tls_hostname: 'smtp.example.com') { ... }
    

    net-smtp gem

    tls_verify: false 라이브러리는 Ruby2.7에서 Gem으로 바뀌었고 새로운 tls_hostname를 사용하려면 다음과 같은 방법으로 사용할 수 있습니다.
    % gem install net-smtp
    
    루비2.7 이전에는 젬이 설치되어 있어도 표준부가라이브러리net/smtp가 사용될 것이다(표준부가라이브러리를 강제로 삭제한 경우net/smtp도 사용할 수 없는 것은 아니다...)
    인증서의 검증 주위에 호환되지 않는 부분이 있으니 조심해서 사용하세요.
    루비의 깃 창고도 넣었기 때문에 특별한 문제가 없다면 루비 3.0에서 이 신규net/smtp가 기준이다.

    좋은 웹페이지 즐겨찾기