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