STARTTLS Server SMTP with cleartext and STARTTLS client support
Problem this snippet solves:
We were looking at our O365 security score and our SMTP scores were pretty sad, so I looked at how I could create a STARTTLS connection up to O365 regardless of the client support. Originally I just supported cleartext clients, but I was able to get it to support clients running STARTTLS too. Links in the iRule to some helpful threads I used while developing this.
How to use this snippet:
Attach this iRule to a SMTP VS with a clientssl, serverssl and a SMTP profile.
Code :
# This iRule allows dynamic cleartext and STARTTLS client connection with a STARTTLS connection to the upstream server # unfortunately, cleartext to server connections do not work with the iRule as-is when CLIENT_ACCEPTED { # No SSL client side, also check no SSL running already on server side # Debug mode set DEBUG 1 set SERVER_SSL 0 set CLIENT_SSL 0 set EHLO_Name "smtp.domain.com" if { $DEBUG } { log local0. "CLIENT_ACCEPTED" } SSL::disable serverside # Disable All TLS so we can dynamically enable it SSL::disable } when SERVER_CONNECTED { if { $DEBUG } { log local0. "SERVER_CONNECTED" } # Start collecting from the server because in SMTP the server responds first TCP::collect } when CLIENT_DATA { set lcpayload [string tolower [TCP::payload]] if { $DEBUG } { log local0.debug "CLIENT_DATA - PAYLOAD - $lcpayload" } if { $lcpayload starts_with "ehlo" } { if { $DEBUG } { log local0.debug "CLIENT_DATA - ehlo" } # https://devcentral.f5.com/s/articles/offload-smtp-encryption-via-irules # Spoof back STARTTLS headers to the client so they'll tell us if the support it or not TCP::respond "250-STARTTLS\r\n250 OK\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::release TCP::collect } elseif { $lcpayload starts_with "starttls" } { if { $DEBUG } { log local0.debug "CLIENT_DATA - Starttls" } # https://devcentral.f5.com/s/articles/smtp-start-tls # Spoof back the 'Ready to start TLS' header to the client so we can do client<->F5 SSL TCP::respond "220 Ready to start TLS\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::release clientside {SSL::enable} } elseif { $lcpayload starts_with "rset" and $SERVER_SSL == 1 } { if { $DEBUG } { log local0.debug "CLIENT_DATA - Client RSET" } # In situations where the client is not encrypting, but the server is, it appears nessecary to reset and re-EHLO to the SMTP server. # In this case, these are the advertisements from the O365 relay so you may have to adjust them according to your SMTP server responses. # This can be attained by setting DEBUG variable to '1' and watching /var/log/ltm when you send a message for the response returned from # the SMTP server for 'Hello'. # Example: Rule /Common/SMTP_STARTTLS: server SSL payload: 250-blah.mail.protection.outlook.com Hello [removed-IP] 250-SIZE 157286400 250-PIPELINING 250-DSN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-BINARYMIME 250-CHUNKING 250 SMTPUTF8 TCP::respond "250-STARTTLS 250-SIZE 157286400 250-PIPELINING 250-DSN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-BINARYMIME 250-CHUNKING 250 SMTPUTF8\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::release TCP::collect # /var/log/ltm complains about this, but it appears to work serverside { SSL::respond "EHLO $EHLO_Name\r\n" } } else { if { $DEBUG } { log local0.debug "CLIENT_DATA - Default release" } TCP::release } } when SERVER_DATA { # Most of thise was helpfully taken from https://devcentral.f5.com/s/questions/need-an-irule-for-starttls-for-smtps-server-side-only-not-client-side # Read in responses from remote server into a variable and log to /var/log/ltm set payload [string tolower [TCP::payload]] if { $DEBUG } { log local0. "SERVER_DATA - PAYLOAD - $payload" } if {$payload starts_with "220" and $payload contains "esmtp"} { # Listen for remote servers opening 220 and esmtp message # NOTE the ‘if’ statement above may need to be tweaked to except what message the other # side is actually sending in reply. Logs should show this. # Respond with a EHLO to server, most servers require a name after the EHLO as well. TCP::respond "EHLO $EHLO_Name\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::release if { $DEBUG } { log local0. "SERVER_DATA - Responded to server with EHLO" } serverside {TCP::collect} } elseif {$payload contains "250-starttls" } { # Check server responds with "250-starttls", if so, respond with a STARTTLS TCP::respond "STARTTLS\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::release if { $DEBUG } { log local0. "SERVER_DATA - Sent the server a STARTTLS" } serverside {TCP::collect} } elseif {$payload contains "220 ready for tls" or $payload contains "220 2.0.0 continue" or $payload contains "220 2.0.0 smtp server ready" } { # if server gives a 220 response, then start server side ssl profile # NOTE the ‘if’ statement above may need to be tweaked to except what message the other # side is actually sending in reply. Logs should show this. ###### # O365 Edit - O365 returns 220 2.0.0 smtp server ready after enabling TLS - Adjust as needed ###### if { $DEBUG } { log local0. "SERVER_DATA - server said he is ready for TLS, enable the SSL profile" } TCP::payload replace 0 [TCP::payload length] "" TCP::release serverside {SSL::enable} # TLS hanshake should now start, which is best seen in wireshark packet captures. } else { if { $DEBUG } { log local0.debug "SERVER_DATA - Default release" } TCP::release clientside { TCP::collect } } } when SERVERSSL_HANDSHAKE { # This will only trigger if that is completed successfully. # ServerSSL profile will need a certificate to match the outbound IP and DNS name, # and you may want to set the "Server certificate" setting to "require", # and the "Trusted Certificate Authorities" set to "ca-bundle". if { $DEBUG } { log local0. "SERVERSSL_HANDSHAKE - SSL handshake completed." } set SERVER_SSL 1 if { $CLIENT_SSL == 1 } { if { $DEBUG } { log local0.debug "SERVERSSL_HANDSHAKE - Client respond SSL" } clientside { SSL::respond "220 SMTP ESMTP Relay F5\r\n" } } else { if { $DEBUG } { log local0.debug "SERVERSSL_HANDSHAKE - Client respond TCP" } clientside { TCP::respond "220 SMTP ESMTP Relay F5\r\n" } # Give the client side a chance to STARTTLS clientside { TCP::collect } } if { $DEBUG } { log local0.debug "SERVERSSL_HANDSHAKE - SSL collect" } SSL::collect } when CLIENTSSL_HANDSHAKE { # This will only trigger if that is completed successfully. # ServerSSL profile will need a certificate to match the outbound IP and DNS name, # and you may want to set the "Server certificate" setting to "require", # and the "Trusted Certificate Authorities" set to "ca-bundle". if { $DEBUG } { log local0. "SSL handshake completed." } set CLIENT_SSL 1 SSL::collect } when SERVERSSL_DATA { # Log the SMTP responses to see any errors. if { $DEBUG } { log local0. "SERVERSSL_DATA - PAYLOAD - [SSL::payload]" } SSL::release if { $CLIENT_SSL == 0 } { if { $DEBUG } { log local0.debug "SERVERSSL_DATA - Client TCP Collect"} clientside { TCP::collect } } SSL::collect } when CLIENTSSL_DATA { # Log the SMTP responses to see any errors. if { $DEBUG } { log local0. "CLIENTSSL_DATA - PAYLOAD - [SSL::payload]" } #log local0.debug "Clientssl_data - release" SSL::release SSL::collect }
Published May 02, 2019
Version 1.0