Forum Discussion
iRule to modify SMTP mail content
Hi, We have an requirement, where in we need to inspect the incoming SMTP message, look for a particular string and based on that modify the mail content. I have written the below iRule but this is not working. The CLIENT_DATA event is being triggered. I refered to the SMTP proxy iRule provided in this link(https://devcentral.f5.com/wiki/iRules.SMTPProxy.ashx?NS=iRules). I am not sure if i am missing anything here.
when CLIENT_ACCEPTED {
TCP::respond "220\r\n" TCP::collect 2000 } when CLIENT_DATA { set cdata [TCP::payload] if { $cdata starts_with "DATA" } { if { [string match "Exchange2010R2" $cdata ]} {
string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} TCP::payload
}
} TCP::release TCP::collect }
Thanks in Advance. Ajay
- Seth_CooperEmployee
I haven't tested this but try something like this below. I would also recommend adding logging statements as you process through and get your iRule working correctly. This will be a timesaver as you troubleshoot and debug.
when CLIENT_ACCEPTED { TCP::respond "220\r\n" TCP::collect 2000 } when CLIENT_DATA { set cdata [TCP::payload] if { $cdata starts_with "DATA" } { if { $cdata contains "Exchange2010R2" } { set new_payload [string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} $cdata] TCP::payload replace 0 0 $new_payload } } TCP::release TCP::collect }
Seth
- cjuniorNacreous
Hi,
I could not test, but basically you have to replace the TCP::payload. I also think that the [string match] you need to change the pattern to "*Exchange2010R2*" As a tip, write some logs with the values of the things on the main points to see if everything is going well and then remove. See if this helps you. []when CLIENT_ACCEPTED { TCP::respond "220\r\n" TCP::collect 2000 } when CLIENT_DATA { set cdata [TCP::payload] if { $cdata starts_with "DATA" } { if { [string match "*Exchange2010R2*" $cdata ]} { TCP::payload replace 0 [TCP::payload length] [string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} [TCP::payload]] } } TCP::release TCP::collect }
- Ajay_Koushik_19Nimbostratus
Apologies for the late response. Thanks for your help Seth & CJunior. The code that we've finally come up with looks like this:
when CLIENT_ACCEPTED { TCP::respond "220\r\n" log local0. "Request from [IP::client_addr]" TCP::collect 2048 } when CLIENT_DATA { set cdata [TCP::payload]
log local0. "Received SMTP DATA..." log local0. "Received Data is ..." log local0. "<$cdata>" if { $cdata contains "Exchange2010R2" } { log local0. "DATA contains Exchange..." TCP::payload replace 0 [TCP::payload length] [string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} [TCP::payload]] } TCP::release TCP::collect
}
This works absolutely fine when i try to send an email via TELNET using EHLO, MAIL FROM: commands etc. But the moment i send an email from Outlook (Exchange server), i encountered a few issues:
- When the payload was less than 2048, the message was stuck in CLIENT_ACCEPTED and was never delivered to the server by F5.
- When the payload is greater than 2048, the message goes into the CLIENT_DATA section. Can see the EHLO, MAIL FROM, RCPT TO printed in the logs. But the moment the DATA section comes up, i encounter a and the connection is closed there (the message has a lot of info in the DATA section). I dont see this after removing the iRule completely from F5.
Have been stuck with this for a long time now. 😞 Thanks in Advance, Ajay
- cjuniorNacreousFor this reason I said that I had doubts about TCP::collect :) What happens if you do not put the tcp collect lenght on the client_accepted event then remove the collect from the client_data event?
- Ajay_Koushik_19Nimbostratus
Tried doing that. If we remove the tcp:collect length from CLIENT_ACCEPTED event, the CLIENT_DATA event is not invoked at all.
- cjuniorNacreous
Hi, Try this:
when CLIENT_ACCEPTED { TCP::respond "220\r\n" TCP::collect } when CLIENT_DATA { set cdata [TCP::payload] log local0. "Old payload: $cdata" switch -glob $cdata { "DATA*" { if { $cdata ends_with "\r\n.\r\n" } { if { [string match "*Exchange2010R2*" $cdata ] } { TCP::payload replace 0 [TCP::payload length] [string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} [TCP::payload]] log local0. "New payload: [TCP::payload]" } TCP::release } } default { if { $cdata ends_with "\r\n" } { TCP::release } } } TCP::collect }
It works for me.
But I've tried only in telnet. :) Hope it helps you now! - Ajay_Koushik_19Nimbostratus
Hi, Thanks for your help :) I tried the code shared by you. It works fine when i test it using TELNET. But when i send an email from the Exchange server, the connection resets in the DATA section and the mail is not delivered to the back end server. The moment i remove the iRule for F5, all emails in the queue start flowing to the backend server. Please find below the logs printed during one such transaction. Thanks
May 7 13:59:39 LABPMCUSEXTLB01 info tmm[14744]: Rule /Common/LAB_SMTP_Enrichment_v2 : Old payload: EHLO AUT22EH000LABWI.domain.com May 7 13:59:39 LABPMCUSEXTLB01 info tmm[14744]: Rule /Common/LAB_SMTP_Enrichment_v2 : Old payload: MAIL FROM: May 7 13:59:39 LABPMCUSEXTLB01 info tmm[14744]: Rule /Common/LAB_SMTP_Enrichment_v2 : Old payload: RCPT TO: May 7 13:59:39 LABPMCUSEXTLB01 info tmm[14744]: Rule /Common/LAB_SMTP_Enrichment_v2 : Old payload: DATA May 7 13:59:39 LABPMCUSEXTLB01 info tmm[14744]: Rule /Common/LAB_SMTP_Enrichment_v2 : Old payload: DATA RSET
- nitassEmployeeis it complete log? i think i do not see "New payload" in the log.
- Ajay_Koushik_19NimbostratusHi, Yes this is the complete log. The "New Payload" is not called because the message does not reach that part of the code. The moment, DATA section starts, we see a RSET and the mail hangs in F5 and is not delivered to the backend server. This only occurs when the message is received from the Exchange server. Works perfectly fine when i tested this from TELNET.
- nitassEmployeecan you try tcpdump? tcpdump -nni 0.0:nnn -s0 -w /var/tmp/output.pcap host x.x.x.x or host y.y.y.y and port 25 -v x.x.x.x is virtual server ip y.y.y.y is pool member ip
- cjuniorNacreous
Hi,
I made some changes to see the response from the server and connect direct too. Could you post again the telnet and exchange logs and if possible the VS configuration? Thxwhen CLIENT_ACCEPTED { log local0. "who was accepted: [IP::client_addr]" LB::connect TCP::collect } when CLIENT_DATA { log local0. "sent payload: [TCP::payload]" switch -glob [TCP::payload] { "DATA*" { if { [TCP::payload] ends_with "\r\n.\r\n" } { if { [string match "*Exchange2010R2*" [TCP::payload]] } { TCP::payload replace 0 [TCP::payload length] [string map {"Sent From:" "Sent From:\nExchange 2010 R2 Server\n"} [TCP::payload]] log local0. "new payload: [TCP::payload]" } TCP::release } } default { if { [TCP::payload] ends_with "\r\n" } { TCP::release } } } TCP::collect } when SERVER_CONNECTED { log local0. "currently server: [LB::server addr]" TCP::collect } when SERVER_DATA { log local0. "received payload: [TCP::payload]" TCP::release TCP::collect }
- Ajay_Koushik_19Nimbostratus
Hi All, Thanks for all the help. I tried the above code and i still get the same issue. The am not able to post the logs here as they are too huge. The switch condition never gets triggered. The moment the DATA starts, i see a RSET in the logs and the mail hangs. I was able to collect the TCP DUMP in F5 with and wo the iRule and i see the below:
Without iRule: C: DATA smtp > 50509 [ACK] Seq=168 Ack=165 Win=4358 Len=0 S: 354 Start mail input; end with . C: DATA fragment, 1398 bytes smtp > 50509 [ACK] Seq=214 Ack=1563 Win=5756 Len=0 .. and the data stream follows
With iRule: C: DATA smtp > 50509 [ACK] Seq=173 Ack=165 Win=4358 Len=0 C: DATA fragment, 6 bytes smtp > 50509 [ACK] Seq=173 Ack=171 Win=5364 Len=0 .. and nothing happens after that
Here it is very clear that with the irule, the F5 never responds with "354 Start mail input; end with .". I guess, the Exchange server is expecting the remote server to respond before sending the data packets and since that does not happen here, we are seeing the hang. Any idea. Thanks, Ajay
- cjuniorNacreous
It is looking like it might not expecting the CR "\r" character. As a last shot, you could remove the carriage return or test it as below:
for "DATA*" part:
if { [TCP::payload] ends_with "\n.\r\n" || [TCP::payload] ends_with "\n.\n" }
for default part
if { [TCP::payload] ends_with "\n" }
The important here is to know who is the message end identifier.
- Ajay_Koushik_19Nimbostratus
Hi All,
Apologies for the late response. We've got this working now using the STREAM_MATCHED event. So basically we check for a STREAM expression, if that matches, then the string replacement happens inside the STREAM_MATCHED event.
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com