An irule to validate client ID via DNS lookup using the stream profile.
Problem this snippet solves:
This irule can be used to validate the ID (hostname or IP addr) an SMTP client claims at the start of the SMTP protocol.
The validation is done via a DNS lookup if the client ID is a name, and unless the name exists in the DNS and its resolved address is identical with the real IP address of the TCP peer, the client connection is dropped.
If the client ID is an address, unless the address is identical with the real IP address of the TCP peer, the client connection is dropped.
Obviously, one would need to edit some of the matching strings if one's SMTP server uses a different lingo in the SMTP dialogue.
(Updated 07-Aug-2014•Originally posted on 07-Aug-2014)
Below was updated 28-Aug-2014•Originally posted on 28-Aug-2014:
In this irule, I define explicitly a route-domain ID in the IP address of my name server in [RESOLV::lookup]. This ought not be necessary as the partition default route domain should be assumed; but it does not work without it. I opened a case with F5 Support and the issue has now been confirmed as a bug (ID 476920).
How to use this snippet:
Code :
when RULE_INIT { set static::smtp_debug 1 set static::route_domain_id 1 } when CLIENT_ACCEPTED { if {[class match [getfield [IP::client_addr] % 1] equals internal_IP]} { } else { drop return } STREAM::expression {@[hH][eE][lL][oO] .*[[:cntrl:]][[:cntrl:]]@@ @[eE][hH][lL][oO] .*[[:cntrl:]][[:cntrl:]]@@ @[.][[:cntrl:]][[:cntrl:]]@@ @354 End data with @@ @250 2.0.0 Ok: queued as @@} STREAM::enable set disable_matching 0 } when STREAM_MATCHED { switch -glob [STREAM::match] { "354 End data with " { incr disable_matching STREAM::replace return } "250 2.0.0 Ok: queued as " { STREAM::replace return } default { set mstring_hex "" set mstring_hex_trimmed "" set mstring_ascii "" set c_initiation_string_hex_trimmed "" set detected_c_initiation_string "" binary scan [STREAM::match] H* mstring_hex set mstring_hex_trimmed [string range $mstring_hex 0 end-4] set mstring_ascii [binary format H* $mstring_hex_trimmed] if { $mstring_ascii eq "\." } { set disable_matching 0 STREAM::replace return } if { $disable_matching > 0 } { STREAM::replace return } if { ( [string tolower $mstring_ascii] starts_with "helo " ) or ( [string tolower $mstring_ascii] starts_with "ehlo " ) } { set c_declared_id [string range $mstring_ascii 5 end] } if { $c_declared_id contains "\[" } { set c_declared_id [ string map [ list \[ "" \] "" ] $c_declared_id ] } set a null set b null set c null set d null scan $c_declared_id {%d.%d.%d.%d} a b d c if { !($a == "null") && !($b == "null") && !($c == "null") && !($d == "null") } { if { (0 <= $a) && ($a <= 255) && (0 <= $b) && ($b <= 255) && (0 <= $c) && ($c <= 255) && (0 <= $d) && ($d <= 255) } { if { $static::route_domain_id != 0 } { append c_declared_id "%" $static::route_domain_id } if { not ( $c_declared_id equals [IP::client_addr] ) } { drop return } } } else { if { ! ( $c_declared_id contains "\." ) } { drop return } if { $static::route_domain_id != 0 } { set resolved_addrs [RESOLV::lookup @172.18.240.210%$static::route_domain_id -a $c_declared_id] } else { set resolved_addrs [RESOLV::lookup @172.18.240.210 -a $c_declared_id] } if { not ( $resolved_addrs equals "" ) } { set addr_matched 0 foreach resolved_addr $resolved_addrs { if { $static::route_domain_id != 0 } { append resolved_addr "%" $static::route_domain_id } if { $resolved_addr equals [IP::client_addr] } { incr addr_matched break } } if { $addr_matched < 1 } { drop return } } else { drop return } } STREAM::replace return } } } when SERVER_CONNECTED { STREAM::expression {@354 End data with @@ @250 2.0.0 Ok: queued as @@} STREAM::enable } ############################################################# # # Below is a modified version after removal of checking of an acl, which is not of general use. # ############################################################# when RULE_INIT { set static::route_domain_id 1 } when CLIENT_ACCEPTED { STREAM::expression {@[hH][eE][lL][oO] .*[[:cntrl:]][[:cntrl:]]@@ @[eE][hH][lL][oO] .*[[:cntrl:]][[:cntrl:]]@@ @[.][[:cntrl:]][[:cntrl:]]@@ @354 End data with @@ @250 2.0.0 Ok: queued as @@} STREAM::enable set disable_matching 0 } when STREAM_MATCHED { switch -glob [STREAM::match] { "354 End data with " { incr disable_matching STREAM::replace return } "250 2.0.0 Ok: queued as " { STREAM::replace return } default { set mstring_hex "" set mstring_hex_trimmed "" set mstring_ascii "" set c_initiation_string_hex_trimmed "" set detected_c_initiation_string "" binary scan [STREAM::match] H* mstring_hex set mstring_hex_trimmed [string range $mstring_hex 0 end-4] set mstring_ascii [binary format H* $mstring_hex_trimmed] if { $mstring_ascii eq "\." } { set disable_matching 0 STREAM::replace return } if { $disable_matching > 0 } { STREAM::replace return } if { ( [string tolower $mstring_ascii] starts_with "helo " ) or ( [string tolower $mstring_ascii] starts_with "ehlo " ) } { set c_declared_id [string range $mstring_ascii 5 end] } if { $c_declared_id contains "\[" } { set c_declared_id [ string map [ list \[ "" \] "" ] $c_declared_id ] } set a null set b null set c null set d null scan $c_declared_id {%d.%d.%d.%d} a b d c if { !($a == "null") && !($b == "null") && !($c == "null") && !($d == "null") } { if { (0 <= $a) && ($a <= 255) && (0 <= $b) && ($b <= 255) && (0 <= $c) && ($c <= 255) && (0 <= $d) && ($d <= 255) } { if { $static::route_domain_id != 0 } { append c_declared_id "%" $static::route_domain_id } if { not ( $c_declared_id equals [IP::client_addr] ) } { drop return } } } else { if { ! ( $c_declared_id contains "\." ) } { drop return } if { $static::route_domain_id != 0 } { set resolved_addrs [RESOLV::lookup @172.18.240.210%$static::route_domain_id -a $c_declared_id] } else { set resolved_addrs [RESOLV::lookup @172.18.240.210 -a $c_declared_id] } if { not ( $resolved_addrs equals "" ) } { set addr_matched 0 foreach resolved_addr $resolved_addrs { if { $static::route_domain_id != 0 } { append resolved_addr "%" $static::route_domain_id } if { $resolved_addr equals [IP::client_addr] } { incr addr_matched break } } if { $addr_matched < 1 } { drop return } } else { drop return } } STREAM::replace return } } } when SERVER_CONNECTED { STREAM::expression {@354 End data with @@ @250 2.0.0 Ok: queued as @@} STREAM::enable }
Tested this on version:
11.6