The initial problem

Today I was putting together an update for my answer on this StackOverflow question, and it occurred to me that (as I’d noted in the edit) it would be possible to create an email that had neither a text/plain part or a text/html part. While I was fairly confident that this would be true, I thought I should quickly confirm this, and the simplest way that I could think of was to send the email through to the SMTP server using netcat.

However, as I’m using Gmail for my email, the SMTP server is only available via secure connection to either port 465 (for SSL connections) or port 587 (for TLS/STARTTLS connections) as shown here. Therefore, I was going to need some kind of SSL tunnel to allow me to connect in plaintext using netcat, but connect to the SMTP server over SSL. From my days using Slackware consistently, I remembered stunnel, which is conveniently available on macOS via brew.

Cheat sheet

$ brew install stunnel
$ cat << EOF > smtps-gmail.conf
> [smtps-gmail]
> accept = localhost:2525
> client = yes 
> connect = smtp.gmail.com:465
> EOF
$ stunnel smtps-gmail.conf
$ nc -c localhost 2525
> EHLO msbit.com.au
> AUTH LOGIN
> <Base64 encoded username>
> <Base64 encoded password>
> MAIL FROM: <tom@msbit.com.au>
> RCPT TO: <tom@msbit.com.au>
> DATA
> Content-Type: application/data; name="data.bin"
> Content-Transfer-Encoding: base64
> <Base64 encoded data>
> .

Making the tunnel

Firstly, we will need to install stunnel, which is as simple as:

$ brew install stunnel

The stunnel configuration file can define multiple services, each with its own local options. In this case, there is only one smtps-gmail, and the configuration defines the endpoint on which to listen:

accept = localhost:2525

the endpoint to connect to:

connect = smtp.gmail.com:465

and the mode in which to operate:

client = yes

which means that the listening connection does not require SSL.

Putting it all together we have:

[smtps-gmail]
accept = localhost:2525
client = yes 
connect = smtp.gmail.com:465

We can now start the tunnel by running:

$ stunnel smtps-gmail.conf

NOTE: The listening port is set to 2525 to ensure it can be bound to as an unprivileged user. If I wanted to use the well-known port for SMTP, port 25, stunnel would need to be run as a privileged user (e.g. via sudo).

Preparing to send

We need to prepare a few items in advance, as the commands are entered interactively:

  • username
  • password
  • message data

All of these involve Base64 encoding to protect the contents during transmission, as SMTP is traditionally not 8-bit clean.

Format username

To prepare the username and password strings:

$ echo -n 'username' | base64

dXNlcm5hbWU=

Format password

$ echo -n 'password' | base64

cGFzc3dvcmQ=

Generate message body

For the purposes of this, I’m going to generate a random file for sending:

$ dd if=/dev/urandom of=data.bin bs=1024 count=1
$ base64 --break 76 data.bin

ypV6owJCIyb+nm5ayZbeeaVp5Wqfxw+VWce+NE2DDAz+LDWCNbFd05LfyyXCVaclsL65Q26ILiHD
RvzMMyRRPyvWvG8q/R3yQcUykFXvTQ4GmOkpMo4hhcqmmfj9blhXT0gTW5UHIMDCSJdvMc8gaS4i
tvEs5ftjCeTMoX1IXOv0N5hDPwue125ZFkG4WCK4LTd44BIRJyVDdMLh+ycVNmCkFq+l3dCTy34r
w2/c9WPpvzo+w1rvA2ryZcd4MXVBbLc7EBA5V0v7IKrCj6af/GmfqulxPWHCwDdhiOc/v1ctO8Q1
jaorupDkRioF3YnN/6SZDJYYxcc5okLRh8DOmYGPlLLx1gthqXeo6BY1wcf5Rh5JiRf6gSgZ2djL
PWBXt0TC96ndK4VT/pxQvShDx/eIG5QSaziNFXQUkVnXfxyazwZdZRgVxdjwrpkM0MZQixVgMPoS
81ucUlhu99SCd+LPfWbsa9Cloyec6GAmfXwvNaYK57g15fTZRm9wftJnzeqavoePIFK9ldgkdgob
Ek2Cm+lEK+2IVmrYg0Y4zWDsuWX01Wf9SKw+QFro4zdMpFrFCB2GAW8ErZrvWE/mjRlXtY4Ja2MN
OQ6KF8pRuzLehNJJhvQJjKGP2RzTqs/OcNnGULOUiy9SIBEnszzhtjqlkMbuZ+cwEKWmRX+/5Wi9
CK28ZxAMSEuAQbCOEEAcKcrqWJKr8bgrOmp0A41IaiZIKzwqkONinCFOEdRSdRvGpJ67oDplvzB6
gLT2Ij+7F5Sc3Yyg00mXK+Zae7bcsMwYVWcH6b3XYRo0LaNUTlCz4oHFWY4/rhhEhVA0zRZpKKYX
H9C7qInKh1+1TJ3hmNW87jrttFft/ggW0LYoTKZEVEAeJHtICJay6GsFaqG6q/KRCdV1ZxfImMgH
gbhgEEzDq2hTpACgBkRu7SUrcKPSzXgBufbUrZwH4EZRcj+x6PRFM0D9EOT+uPQxK3EBNmTjwwLa
uYpP0hDg84o4e7IkCbBHdNNc1PK1YuPXlKjIyNQd5kOQxJJY8+GVgA5IGTrSWUitCh/yW7U8Px7B
Mw7xMKxAdu4nBptvS8mgc1ueikyRGVPTv3NJ44jO6IBCqce3a0yGXLZ3DNc1p0IR1MKe6ta584Fd
bCw5/3/QowqroFMEORxdq/UY7Flea4FufoVCmAhHOVm/W7s4F/ZnF7sUBH0GV8b5cnLmpn1KPsKK
hA4jqT+KrofjiPreu99qGkv01oxPAPLDGNPU7ahCPETK6bznGMO3Fw8dZW9ElonZywP99P8UQU7q
7QP1KMA8EdB0DDdtLpvQeuHWBv3aBgwRnx1HC44fs6yzCVn9H5/SfDUyWh29dWJZWpRBM7If4Q==

The simplest format for the body of the message specifies the Content-Type, the Content-Transfer-Encoding and provides the encoded file. Given that this is for testing sending an email without parts of type text/plain or text/html, the message body will be as follows:

Content-Type: application/data; name="data.bin"
Content-Transfer-Encoding: base64
ypV6owJCIyb+nm5ayZbeeaVp5Wqfxw+VWce+NE2DDAz+LDWCNbFd05LfyyXCVaclsL65Q26ILiHD
RvzMMyRRPyvWvG8q/R3yQcUykFXvTQ4GmOkpMo4hhcqmmfj9blhXT0gTW5UHIMDCSJdvMc8gaS4i
tvEs5ftjCeTMoX1IXOv0N5hDPwue125ZFkG4WCK4LTd44BIRJyVDdMLh+ycVNmCkFq+l3dCTy34r
w2/c9WPpvzo+w1rvA2ryZcd4MXVBbLc7EBA5V0v7IKrCj6af/GmfqulxPWHCwDdhiOc/v1ctO8Q1
jaorupDkRioF3YnN/6SZDJYYxcc5okLRh8DOmYGPlLLx1gthqXeo6BY1wcf5Rh5JiRf6gSgZ2djL
PWBXt0TC96ndK4VT/pxQvShDx/eIG5QSaziNFXQUkVnXfxyazwZdZRgVxdjwrpkM0MZQixVgMPoS
81ucUlhu99SCd+LPfWbsa9Cloyec6GAmfXwvNaYK57g15fTZRm9wftJnzeqavoePIFK9ldgkdgob
Ek2Cm+lEK+2IVmrYg0Y4zWDsuWX01Wf9SKw+QFro4zdMpFrFCB2GAW8ErZrvWE/mjRlXtY4Ja2MN
OQ6KF8pRuzLehNJJhvQJjKGP2RzTqs/OcNnGULOUiy9SIBEnszzhtjqlkMbuZ+cwEKWmRX+/5Wi9
CK28ZxAMSEuAQbCOEEAcKcrqWJKr8bgrOmp0A41IaiZIKzwqkONinCFOEdRSdRvGpJ67oDplvzB6
gLT2Ij+7F5Sc3Yyg00mXK+Zae7bcsMwYVWcH6b3XYRo0LaNUTlCz4oHFWY4/rhhEhVA0zRZpKKYX
H9C7qInKh1+1TJ3hmNW87jrttFft/ggW0LYoTKZEVEAeJHtICJay6GsFaqG6q/KRCdV1ZxfImMgH
gbhgEEzDq2hTpACgBkRu7SUrcKPSzXgBufbUrZwH4EZRcj+x6PRFM0D9EOT+uPQxK3EBNmTjwwLa
uYpP0hDg84o4e7IkCbBHdNNc1PK1YuPXlKjIyNQd5kOQxJJY8+GVgA5IGTrSWUitCh/yW7U8Px7B
Mw7xMKxAdu4nBptvS8mgc1ueikyRGVPTv3NJ44jO6IBCqce3a0yGXLZ3DNc1p0IR1MKe6ta584Fd
bCw5/3/QowqroFMEORxdq/UY7Flea4FufoVCmAhHOVm/W7s4F/ZnF7sUBH0GV8b5cnLmpn1KPsKK
hA4jqT+KrofjiPreu99qGkv01oxPAPLDGNPU7ahCPETK6bznGMO3Fw8dZW9ElonZywP99P8UQU7q
7QP1KMA8EdB0DDdtLpvQeuHWBv3aBgwRnx1HC44fs6yzCVn9H5/SfDUyWh29dWJZWpRBM7If4Q==

Sending the email

Now that we have the tunnel running and our encoded contents prepared, we can connect through it to the SMTP server by running:

$ nc -c localhost 2525

NOTE: On macOS, the netcat command has the -c option, which sends CRLF as the line-ending. This is important when entering the body of the email, as the end of the data is expected to be exactly <CR><LF>.<CR><LF>.

We are greeted with the service ready message:

220 smtp.gmail.com ESMTP v17-v6sm3241947pfn.177 - gsmtp

From here, we interactively enter the SMTP commands effectively in a script as expected by the server:

EHLO msbit.com.au

250-smtp.gmail.com at your service, [xxx.xxx.xxx.xxx]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8

AUTH LOGIN

334 VXNlcm5hbWU6

dXNlcm5hbWU=

334 UGFzc3dvcmQ6

cGFzc3dvcmQ=

235 2.7.0 Accepted

MAIL FROM: <tom@msbit.com.au>

250 2.1.0 OK v17-v6sm3241947pfn.177 - gsmtp

RCPT TO: <tom@msbit.com.au>

250 2.1.5 OK v17-v6sm3241947pfn.177 - gsmtp

DATA

354  Go ahead v17-v6sm3241947pfn.177 - gsmtp

Content-Type: application/data; name="data.bin"
Content-Transfer-Encoding: base64
ypV6owJCIyb+nm5ayZbeeaVp5Wqfxw+VWce+NE2DDAz+LDWCNbFd05LfyyXCVaclsL65Q26ILiHD
RvzMMyRRPyvWvG8q/R3yQcUykFXvTQ4GmOkpMo4hhcqmmfj9blhXT0gTW5UHIMDCSJdvMc8gaS4i
tvEs5ftjCeTMoX1IXOv0N5hDPwue125ZFkG4WCK4LTd44BIRJyVDdMLh+ycVNmCkFq+l3dCTy34r
w2/c9WPpvzo+w1rvA2ryZcd4MXVBbLc7EBA5V0v7IKrCj6af/GmfqulxPWHCwDdhiOc/v1ctO8Q1
jaorupDkRioF3YnN/6SZDJYYxcc5okLRh8DOmYGPlLLx1gthqXeo6BY1wcf5Rh5JiRf6gSgZ2djL
PWBXt0TC96ndK4VT/pxQvShDx/eIG5QSaziNFXQUkVnXfxyazwZdZRgVxdjwrpkM0MZQixVgMPoS
81ucUlhu99SCd+LPfWbsa9Cloyec6GAmfXwvNaYK57g15fTZRm9wftJnzeqavoePIFK9ldgkdgob
Ek2Cm+lEK+2IVmrYg0Y4zWDsuWX01Wf9SKw+QFro4zdMpFrFCB2GAW8ErZrvWE/mjRlXtY4Ja2MN
OQ6KF8pRuzLehNJJhvQJjKGP2RzTqs/OcNnGULOUiy9SIBEnszzhtjqlkMbuZ+cwEKWmRX+/5Wi9
CK28ZxAMSEuAQbCOEEAcKcrqWJKr8bgrOmp0A41IaiZIKzwqkONinCFOEdRSdRvGpJ67oDplvzB6
gLT2Ij+7F5Sc3Yyg00mXK+Zae7bcsMwYVWcH6b3XYRo0LaNUTlCz4oHFWY4/rhhEhVA0zRZpKKYX
H9C7qInKh1+1TJ3hmNW87jrttFft/ggW0LYoTKZEVEAeJHtICJay6GsFaqG6q/KRCdV1ZxfImMgH
gbhgEEzDq2hTpACgBkRu7SUrcKPSzXgBufbUrZwH4EZRcj+x6PRFM0D9EOT+uPQxK3EBNmTjwwLa
uYpP0hDg84o4e7IkCbBHdNNc1PK1YuPXlKjIyNQd5kOQxJJY8+GVgA5IGTrSWUitCh/yW7U8Px7B
Mw7xMKxAdu4nBptvS8mgc1ueikyRGVPTv3NJ44jO6IBCqce3a0yGXLZ3DNc1p0IR1MKe6ta584Fd
bCw5/3/QowqroFMEORxdq/UY7Flea4FufoVCmAhHOVm/W7s4F/ZnF7sUBH0GV8b5cnLmpn1KPsKK
hA4jqT+KrofjiPreu99qGkv01oxPAPLDGNPU7ahCPETK6bznGMO3Fw8dZW9ElonZywP99P8UQU7q
7QP1KMA8EdB0DDdtLpvQeuHWBv3aBgwRnx1HC44fs6yzCVn9H5/SfDUyWh29dWJZWpRBM7If4Q==
.

250 2.0.0 OK 1533692605 v17-v6sm3241947pfn.177 - gsmtp

QUIT

221 2.0.0 closing connection v17-v6sm3241947pfn.177 - gsmtp

Note the final . line to end the DATA portion.

The email should now have been sent!

Confirming the output

When checking my inbox, there is a new email from me, with no subject, or body, but an attachment. The full email message is as shown below:

Bcc: tom@msbit.com.au
Return-Path: <tom@msbit.com.au>
Received: from msbit.com.au (xxx.xxx.xxx.xxx.xxx. [xxx.xxx.xxx.xxx])
        by smtp.gmail.com with ESMTPSA id v17-v6sm3241947pfn.177.2018.08.07.18.43.09
        for <tom@msbit.com.au>
        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
        Tue, 07 Aug 2018 18:43:25 -0700 (PDT)
Message-ID: <5b6a4abd.1c69fb81.319ec.8e62@mx.google.com>
Date: Tue, 07 Aug 2018 18:43:25 -0700 (PDT)
From: tom@msbit.com.au
Content-Type: application/data; name="data.bin"
Content-Transfer-Encoding: base64

ypV6owJCIyb+nm5ayZbeeaVp5Wqfxw+VWce+NE2DDAz+LDWCNbFd05LfyyXCVaclsL65Q26ILiHD
RvzMMyRRPyvWvG8q/R3yQcUykFXvTQ4GmOkpMo4hhcqmmfj9blhXT0gTW5UHIMDCSJdvMc8gaS4i
tvEs5ftjCeTMoX1IXOv0N5hDPwue125ZFkG4WCK4LTd44BIRJyVDdMLh+ycVNmCkFq+l3dCTy34r
w2/c9WPpvzo+w1rvA2ryZcd4MXVBbLc7EBA5V0v7IKrCj6af/GmfqulxPWHCwDdhiOc/v1ctO8Q1
jaorupDkRioF3YnN/6SZDJYYxcc5okLRh8DOmYGPlLLx1gthqXeo6BY1wcf5Rh5JiRf6gSgZ2djL
PWBXt0TC96ndK4VT/pxQvShDx/eIG5QSaziNFXQUkVnXfxyazwZdZRgVxdjwrpkM0MZQixVgMPoS
81ucUlhu99SCd+LPfWbsa9Cloyec6GAmfXwvNaYK57g15fTZRm9wftJnzeqavoePIFK9ldgkdgob
Ek2Cm+lEK+2IVmrYg0Y4zWDsuWX01Wf9SKw+QFro4zdMpFrFCB2GAW8ErZrvWE/mjRlXtY4Ja2MN
OQ6KF8pRuzLehNJJhvQJjKGP2RzTqs/OcNnGULOUiy9SIBEnszzhtjqlkMbuZ+cwEKWmRX+/5Wi9
CK28ZxAMSEuAQbCOEEAcKcrqWJKr8bgrOmp0A41IaiZIKzwqkONinCFOEdRSdRvGpJ67oDplvzB6
gLT2Ij+7F5Sc3Yyg00mXK+Zae7bcsMwYVWcH6b3XYRo0LaNUTlCz4oHFWY4/rhhEhVA0zRZpKKYX
H9C7qInKh1+1TJ3hmNW87jrttFft/ggW0LYoTKZEVEAeJHtICJay6GsFaqG6q/KRCdV1ZxfImMgH
gbhgEEzDq2hTpACgBkRu7SUrcKPSzXgBufbUrZwH4EZRcj+x6PRFM0D9EOT+uPQxK3EBNmTjwwLa
uYpP0hDg84o4e7IkCbBHdNNc1PK1YuPXlKjIyNQd5kOQxJJY8+GVgA5IGTrSWUitCh/yW7U8Px7B
Mw7xMKxAdu4nBptvS8mgc1ueikyRGVPTv3NJ44jO6IBCqce3a0yGXLZ3DNc1p0IR1MKe6ta584Fd
bCw5/3/QowqroFMEORxdq/UY7Flea4FufoVCmAhHOVm/W7s4F/ZnF7sUBH0GV8b5cnLmpn1KPsKK
hA4jqT+KrofjiPreu99qGkv01oxPAPLDGNPU7ahCPETK6bznGMO3Fw8dZW9ElonZywP99P8UQU7q
7QP1KMA8EdB0DDdtLpvQeuHWBv3aBgwRnx1HC44fs6yzCVn9H5/SfDUyWh29dWJZWpRBM7If4Q==

As we can see, the email has been delivered, the content is exactly the same as what was sent, and there is no mention of a text/plain or text/html component. As a final test, we can download the data.bin attachment and compare the SHA hashes:

$ shasum data.bin
e54b3d70b1368829d5db4c9ea77f2f667adf5b59  data.bin
$ shasum ~/Downloads/data.bin
e54b3d70b1368829d5db4c9ea77f2f667adf5b59  /Users/tom/Downloads/data.bin

which also confirm that the file has been successfully sent.