0

I'm writing a Ruby script that send SMTP email. The email is divided onto 3 parts: headers, body and attachment. Unfortunatly, I'm struggling to satisfy an Amavis requirement: properly writing the encapsulation boundary.

This header, added by Amavis, noticed the issue:

X-Amavis-Alert: BAD HEADER SECTION, MIME error: error: multipart boundary is missing, or contains CR or LF

I've read the section 7_2 of the RFC 1341, which defines the multipart syntax.

Note that the encapsulation boundary must occur at the beginning of a line, i.e., following a CRLF, and that that initial CRLF is considered to be part of the encapsulation boundary rather than part of the preceding part. The boundary must be followed immediately either by another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part (and it is therefore assumed to be of Content-Type text/plain).

  • encapsulation boundary must follow a CRLF
  • encapsulation boundary must be followed immediatly by another CRLF + the header fields of the next part

I believe my script is respecting these two rules, however, some mail clients doesn't recognize the attachment. Here's the raw email (notice that each encapsulation boundaries are preceeded and followed by a CRLF):

From: from@domain.tld\nTo: to@domain.tld\nSubject: =?UTF-8?B?RMOpY2xhcmF0aW9uIHNpbXBsaWZpw6llIGRlIHZpb2xlbmNl?=\nDate: 2020-12-17 15:59:14 +0100\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\nContent-Type: text/html;charset=\"utf-8\"\nContent-Transfer-Encoding:utf8\r\n\nEMAIL BODY CONTENT HERE\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7\r\nContent-Type: multipart/mixed; name = \"declaration_171220_155914166.pdf\"\nContent-Transfer-Encoding:base64\nContent-Disposition: attachment; filename = declaration_171220_155914166.pdf\n\nJVBERi0xLjMKJf///...(<- Base64 encoded attachment)...\r\n--_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7--

What am I doing wrong ? What do you think prevents intolerant clients to display the attachment ?


If it can help anyone, here's my Ruby script
require 'base64'

module Reports
  module SMTP
    BOUNDARY = '_3c7d2a21904930ec7ff47d0cb268c6605a8d06c02dc50e0c5498926371fae06a68d7'

    def self.send_report(file_path)
      file_content    = File.binread(file_path)
      encoded_content = [file_content].pack('m')   # Base64
      email_content   = headers + attachment(file_path, encoded_content) + body

      begin
        Net::SMTP.start('groupware.sdis21.org', 25, 'HELO FQDN', 'username', 'password', :plain) do |smtp|
          smtp.send_message(email_content, 'from@domain.tld', ['to@domain.tld'])
        end
      rescue => e
        puts e.inspect, e.backtrace
      end
    end

    def self.headers
      <<~EOF
        From: from@domain.tld
        To: to@domain.tld
        Subject: =?UTF-8?B?#{Base64.strict_encode64('Déclaration simplifiée de violence')}?=
        Date: #{Time.now.to_s}
        MIME-Version: 1.0
        Content-Type: multipart/mixed; boundary=#{BOUNDARY}\r
        --#{BOUNDARY}\r
      EOF
    end

    def self.body
      <<~EOF
        Content-Type: text/html;charset="utf-8"
        Content-Transfer-Encoding:utf8

        EMAIL BODY CONTENT HERE\r
        --#{BOUNDARY}\r
      EOF
    end

    def self.attachment(file_path, encoded_content)
      file_path = file_path.split('/').last
      <<~EOF
        Content-Type: multipart/mixed; name = "#{file_path}"
        Content-Transfer-Encoding:base64
        Content-Disposition: attachment; filename = #{file_path}

        #{encoded_content}\r
        --#{BOUNDARY}--\r
      EOF
    end
  end
end
Sumak
  • 121
  • 6
  • 1
    have you try droping all `\r` ? I never bother to include them. – Archemar Dec 17 '20 at 15:41
  • Yes I did, the first version of the script didn't include any `\r`. Though, after inspecting the raw content of the email, I noticed that boundaries was preceeded by `\n` only. I'm posting an answer to show the solution I've just found – Sumak Dec 17 '20 at 15:54
  • You right, `\r` were useless, removing them causes no difference. Thanks for your comment – Sumak Dec 17 '20 at 16:14
  • What you write in Ruby is not what the mail server sees. `Net::SMTP` silently replaces all lone CR and LF with CRLF because, duh, who uses 8bit anyway. – anx Dec 17 '20 at 16:16
  • obviously you find the issue, despite RFC sendmail, postfix and friend know how to deal with end of line (windows, unix or mac). this is seldom an issue as you found out (I had similar case with openssl (see https://security.stackexchange.com/questions/69189/how-to-sign-and-encrypt-mail-using-openssl/69366#69366)) – Archemar Dec 17 '20 at 20:39

1 Answers1

1

The issue was that the webclient we're using is expecting a correct Content-Type header for the attachment:

Content-Type: application/pdf; name="filename.pdf"

To figure out this, I looked at some emails I've received from the postfix server (/var/spool/imap/our_domain/...) and compared it to the raw email I'm producing with my script. The most noticeable difference was this header.

Sumak
  • 121
  • 6