For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

SMTP filter and forward proxy

Problem this snippet solves:

This iRule provides a forward proxy for SMTP messages; it also filters messages by limiting the maximum size of the DATA sent, and also limiting the amount of messages per TCP connection.

The iRule is limited to a single RCPT at the moment... I'm not sure this could have multiple RCPTs as this would need support of multiple server connections, and a way to store the DATA which doesn't sound like a good thing to do. The other limitation is that it does not support pipelining; I think this is something that could be done in the next version, but will complicate the iRule somewhat.

WARNING: This iRule has NOT been tested in earnest.

Code :

when RULE_INIT {
# The following parameter enables local logging - 0=off, 1=on
set static::SMTPfiltprxdebug 1
# Set maximum message size in bytes (21504000 is 21Mbyte)
set static::maxMsgLen 21504000
# Set maximum number of messages in a single TCP connection (20)
set static::maxNumMsgs 20
#
# High speed logging setup - local7.info
set static::bigip [info hostname]
set static::facility <190>
set static::hsl_prefix "$static::facility|host=$static::bigip"
}

when CLIENT_ACCEPTED {
# Open a connection for high speed logging to hsl_syslog_pool & define log prefix
set hsl [HSL::open -proto UDP -pool hsl_syslog_pool]
set hsl_prefix "${static::hsl_prefix}|client=[IP::client_addr]:[TCP::client_port]"
# Set up a variable to collect number of RCPTs
set numRcpts 0
# Set up variables to collect length of email
set dataFlag 0
set msgLen 0
# Set up variable to collect number of messages in TCP connection
set numMsgs 0
# Set up other variables use to collect payload until we have a RCPT to act upon
set heloCmd ""
set mailCmd ""
set rcptCmd ""
set destIP ""
set smtpSeq 0
# Send back a SMTP service ready and collect client data
TCP::respond "220 Proxy Ready\r\n"
TCP::collect
}

when CLIENT_DATA {
if { [string length [TCP::payload]] <= 0 } {
return
}
if { not ( [TCP::payload] contains "\r\n" ) } {
return
}
switch -glob {[string tolower [TCP::payload]]} {
rset* {
# Blank out payload
TCP::payload replace 0 [string length [TCP::payload]] ""
# Send back an SMTP OK
TCP::respond "250 OK\r\n"
TCP::collect
return
}
helo* { 
# Store the command
set heloCmd [TCP::payload]
# Blank out payload
TCP::payload replace 0 [string length $heloCmd] ""
# Send back an SMTP OK
TCP::respond "250 Hello client at [IP::client_addr]\r\n"
TCP::collect
return
}
ehlo* {
# Store the command
set heloCmd [TCP::payload]
# Blank out payload
TCP::payload replace 0 [string length $heloCmd] ""
# Send back an SMTP OK
TCP::respond "250 Hello client at [IP::client_addr] \r\n"
# Restart collection
TCP::collect
return
}
mail* { 
# Release any server connection if applicable
LB::detach
# MAIL FROM is first command of every message so this is a good place reset variables
set numRcpts 0
set dataFlag 0
set msgLen 0
set smtpSeq 0
# Increment number of messages
incr numMsgs 1
# Store the command
set mailCmd [TCP::payload]
# Blank out payload
TCP::payload replace 0 [string length $mailCmd] ""
# Send back an SMTP OK
TCP::respond "250 sender ok\r\n"
# Restart collection
TCP::collect
return
}
rcpt* {
incr numRcpts 1
# Check to ensure the maximum number of recipients has not been breached
if { $numRcpts > 1 } {
# This session is going to be closed down as too many RCPTs have been detected
set log_message "event=CLIENT_DATA|desc=Over $static::maxNumRcpts recipients detected - session aborted|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
reject
return
}
# Store the command
set rcptCmd [TCP::payload]
# Need to get the RCPT domain and lookup onward host
set rcptDomain [lindex [split [lindex [split [lindex [split [lindex [split $rcptCmd ":"] 1] "@"] 1] "\r\n"] 0] ">"] 0]
# Use RCPT domain to find mail host
set mailSrvs [RESOLV::lookup @/Common/dns -mx $rcptDomain]
#  NB: If there are multiple entries it could be a TCL list.
#  Check if the first list element was empty
if { $mailSrvs equals "" } {
reject
set log_message "event=CLIENT_DATA|desc=DNS lookup (MX) failed for $rcptDomain|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
return
} else {
# Select the 1st returned mail server name and find IP address
set destHost [lindex $mailSrvs 1]
set ips [RESOLV::lookup @/Common/dns -a $destHost]
# Again this could be a TCL list
if { $ips equals "" } {
reject
set log_message "event=CLIENT_DATA|desc=DNS lookup (A) failed for $destHost|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
return
} else {
set destIP [lindex $ips 0]
}
}
# Blank out payload
TCP::payload replace 0 [string length $rcptCmd] ""
# Connect to server and release stored SMTP messages
set log_message "event=CLIENT_DATA|desc=Connection to server $destIP|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
node $destIP 25
}
data* { set dataFlag 1 }
}
# Start counting payload lengths once the DATA command has been seen
if { $dataFlag ne "0" } {
incr msgLen [TCP::payload length]
}
# Check to ensure the maximum message size has not been breached
if { $msgLen > $static::maxMsgLen } {
# This session is going to be closed down as it is too big
set log_message "event=CLIENT_DATA|desc=Over $static::maxMsgLen byte message size detected - session aborted|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
reject
return
}
# Check to ensure the maximum number of messages has not been breached
if { $numMsgs > $static::maxNumMsgs } {
#This session is going to be closed down as too many messages have been attempted
set log_message "event=CLIENT_DATA|desc=Over $static::maxNumMsgs messages detected - session aborted|\n"
HSL::send $hsl "$hsl_prefix|$log_message"
if { $static::SMTPfiltprxdebug ne "0" } {
log local0. "$log_message"
}
reject
return
}
    TCP::release
    TCP::collect
}

when SERVER_CONNECTED {
TCP::collect
}

when SERVER_DATA {
switch -glob [string tolower [TCP::payload]] {
220* {
# Suppress message to client
TCP::payload replace 0 [string length [TCP::payload]] ""
# Send stored HELO command to server
TCP::respond $heloCmd
# Restart collection
TCP::collect
return
}
250* {
incr smtpSeq 1
switch $smtpSeq {
1 {
# Suppress message to client
TCP::payload replace 0 [string length [TCP::payload]] ""
# First time we see this we need to send MAIL command
TCP::respond $mailCmd
# Restart collection
TCP::collect
return
}
2 {
# Suppress message to client
TCP::payload replace 0 [string length [TCP::payload]] ""
# First time we see this we need to send MAIL command
TCP::respond $rcptCmd
# Restart collection
TCP::collect
return
}
}

}
}
TCP::release
}
Published Mar 18, 2015
Version 1.0
No CommentsBe the first to comment