18-May-2014
06:39
- last edited on
22-Nov-2022
07:31
by
JimmyPackets
When SNATs are used for a virtual server, the backend SMTP servers cannot get the client IP address. This irule is intended to replace the string after "EHLO" or "HELO" in mail client initiation with the client's real IP address. For us, this could enable us to track down an offending mail originating device.
when CLIENT_ACCEPTED {
set c-addr [IP::client_addr]
log local0. "Client addr: $c-addr"
}
when CLIENT_DATA {
STREAM::expression {@^EHLO.*\r\n@@ @^HELO.*\r\n@@}
STREAM::enable
event STREAM_MATCHED enable
}
when STREAM_MATCHED {
set mstring [STREAM::match]
log local0. "STREAM_MATCHED: string: $mstring"
if {$mstring starts_with "EHLO"} {
set replacment "EHLO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
}
if {$mstring starts_with "HELO"} {
set replacment "HELO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
}
event STREAM_MATCHED disable
}
when SERVER_DATA {
STREAM::disable
}
This is just an idea at this moment, and I won't be able to test the code until I find a suitable test environment for it; but for now, any comment is welcome as to if this will work at all and if yes what can be improved. Thanks.
18-May-2014
07:15
- last edited on
22-Nov-2022
07:31
by
JimmyPackets
This is pretty nice.
CLIENT_DATA and SERVER_DATA are not going to trigger on their own. You have to initiate TCP::collect in the CLIENT_ACCEPTED or SERVER_CONNECTED. That said, you don't need these events.
Also, "event STREAM_MATCHED enable" is not needed. When there is a match, this event will fire.
Simplify the code under STREAM_MATCHED for easier troubleshooting:
when STREAM_MATCHED {
set mstring [STREAM::match]
log local0. "STREAM_MATCHED: string: $mstring"
set replacment [string range $mstring 0 1]
append replacment "LO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
}
Don't forget the generic (empty) STREAM profile to be added to the virtual. Here is a good link for more info: https://devcentral.f5.com/articles/ltm-stream-profile-multiple-replacements-regular-expressions.U3i7...
Finally, why not test this using a mocked up SMTP server. Setup up an echo server and use telnet for a client. See if the BigIP does the replacement.
18-May-2014
08:07
- last edited on
02-Jun-2023
14:30
by
JimmyPackets
Thanks so much, John! Here's a new version of it based on your advice:
when CLIENT_ACCEPTED {
set c-addr [IP::client_addr]
log local0. "Client addr: $c-addr"
STREAM::expression {@^EHLO.*\r\n@@ @^HELO.*\r\n@@}
STREAM::enable
}
when STREAM_MATCHED {
set mstring [STREAM::match]
log local0. "STREAM_MATCHED: string: $mstring"
set replacment [string range $mstring 0 1]
append replacment "LO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
event STREAM_MATCHED disable
}
when SERVER_CONNECTED {
STREAM::disable
}
I can't test this (it compiled alright) yet because my devices are all firewall'ed off at the moment.
18-May-2014
08:34
- last edited on
02-Jun-2023
14:30
by
JimmyPackets
The SERVER_CONNECTED event is not needed.
One more thing. In the STREAM::replace command, only the replacement is needed.
STREAM::replace $replacment
Now test it by using a linux server behind a BigIP virtual and netcat such as in these examples.
http://stackoverflow.com/questions/8375860/echo-server-with-bash
You should be able to use telnet and type:
HELO 1.2.3.4
or EHLO 1.2.3.4and it echos back "xxLO [your IP address]"
18-May-2014 13:54
Just a thought... But why don't you add a received: header? That way the sending IP is available for anything you want to put in the sequence (e.g. spamassassin etc).
H
18-May-2014 19:08
To accommodate multiple messages, check out this SMTP proxy iRule example. https://devcentral.f5.com/wiki/iRules.SMTPProxy.ashx
It uses [TCP::collect] and the CLIENT_DATA event as well as [TCP::payload replace] instead of [STREAM::replace]. It offers more flexibility and allows multiple messages search/replace operations.
19-May-2014
04:57
- last edited on
05-Jun-2023
14:09
by
JimmyPackets
Take out the \r\n from the expression. The (.*) should match that as well.
STREAM::expression {@^EHLO.*@@ @^HELO.*@@}
Here is an example:
(System32) 30 % regexp {EHLO.*} "EHLO 1.1.1.1\r\n" var1
1
(System32) 31 % puts $var1
EHLO 1.1.1.1
(System32) 32 %
25-May-2014
19:53
- last edited on
02-Jun-2023
14:23
by
JimmyPackets
I have finally had a chance to actually test an SMTP irule using the stream profile, and here's my latest version, which seems to work, at least under v10.2.4:
when RULE_INIT {
set static::smtp_debug 1
}
when CLIENT_ACCEPTED {
set caddr [IP::client_addr]
if { ${static::smtp_debug} } { log local0. "Client addr: $caddr" }
STREAM::expression {@[hH][eE][lL][oO] @@ @[eE][hH][lL][oO] @@ @354 End data with @354 End data with @ @250 2.0.0 Ok: queued as @250 2.0.0 Ok: queued as @}
STREAM::enable
set end_data_with_seen 0
}
when STREAM_MATCHED {
set mstring [STREAM::match]
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: string: \"$mstring\"" }
if { $mstring contains "354 End data with " } {
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: end_data_with_seen incremented." }
incr end_data_with_seen
return
} elseif { $mstring starts_with "250 2.0.0 Ok: queued as " } {
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: seen queued_as: closing conn." }
TCP::close
return
} else {
if { $end_data_with_seen < 1 } {
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: init string seen: end_data_with_seen is 0." }
set replacement [string range $mstring 0 1]
append replacement "LO \[$caddr\]"
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: replacement string: \"$replacement\"" }
STREAM::replace $replacement
} else {
if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: end_data_with_seen: not 0: stream disabled." }
STREAM::disable
}
}
}
when SERVER_CONNECTED {
STREAM::expression {@[hH][eE][lL][oO] @@ @[eE][hH][lL][oO] @@ @354 End data with @354 End data with @ @250 2.0.0 Ok: queued as @250 2.0.0 Ok: queued as @}
STREAM::enable
}
Of course it is not perfect, and I don't think it can be perfect, as it seems we have run into the limitations of the BRE library the stream profile uses, or of my understanding of how to use the stream functionality.
As a result, I have to terminate the connection after a mail message is delivered to the backend SMTP server in this irule.
Some of the text strings in the regex expressions are specific to my mail server in order to minimize the chances of unexpected/incorrect/unwanted matching.
08-Aug-2014 02:42
I have finally decided to change to another approach: to validate client's ID (hostname or IP addr) via DNS lookup and make a deny/allow decision based on the result of the lookup. The irule is here:
Editors note: Updated this link to point to the new Codeshare @JG created.
An irule to validate client ID via DNS lookup using the stream profile. DevCentral (f5.com)
29-Jun-2021 10:31
The link for the irule needs fixing as I don't find it even in the search in Devcentral.
29-Jun-2021 18:35
I am trying to get this back.
01-Jul-2021 01:33
Found it in the archives:
http://web.archive.org/web/20171128045302/https:/devcentral.f5.com/questions/an-irule-to-validate-client-id-via-dns-lookup-using-the-stream-profile
01-Jul-2021 02:27
Thanks! You can reshare the code in code share but I will attach it here just in case:
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
}
13-Jul-2021 13:15
JG added it to codeshare here:
An irule to validate client ID via DNS lookup using the stream profile. DevCentral (f5.com)