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.0joelmoxey
Nimbostratus
Joined February 14, 2012
joelmoxey
Nimbostratus
Joined February 14, 2012
No CommentsBe the first to comment