XML_ Patcher_iRule
Problem this snippet solves:
This iRule identifies if the client is a PN Agent. If so, the connection is handled differently than normal Web Interface users. Among other things, the authentication credentials are received via headers and need to be passed through. This is also correlated with the APM session information.
This iRule is part of the F5 Deployment Guide "Deploying the BIG-IP APM Secure Proxy with Citrix XenAPP" which can be found in Resources section of f5.com
Please see the deployment guide for full instructions on how to install, use and configure this iRule.
How to use this snippet:
Notes:
It might be good to limit payload collection to 1Mb regardless of what the Content-Length header value is set to. Collecting over 1Mb of data can result in a TMM crash as described in the CreditCardScrubber example and SOL6578 - TMM will crash if an iRule collects more than 4MB of data
# Only check responses that are a text content type (text/html, text/xml, text/plain, etc). if { [HTTP::header "Content-Type"] starts_with "text/" } { # Get the content length so we can collect the data (to be processed in the HTTP_RESPONSE_DATA event) # Limit collection to 1Mb (1048576 minus a little to spare) - See SOL6578 for details. if { [HTTP::header exists "Content-Length"] } { if { [HTTP::header "Content-Length"] > 1048000 }{ # Content-Length over 1Mb so collect 1Mb set content_length 1048000 } else { # Content-Length under 1Mb so collect actual length set content_length [HTTP::header "Content-Length"] } } else { # Response did not have Content-Length header, so use default of 1Mb set content_length 1048000 } # Don't collect content if Content-Length header value was 0 if { $content_length > 0 } { HTTP::collect $content_length } }
Also, you can string commands to parse the username and password fields from the payload more efficient than regexp:
when RULE_INIT { set xml_str { test1 line2 my_user line4 my_pass more text } # Parse the value between and the next < log local0. "username raw: [findstr $xml_str "" 10 "<"]" # Parse the value between and the next < log local0. "password raw: [findstr $xml_str "" 10 "<"]" # Parse the value between and the next < # Parse the value between and the next < # removing any leading or trailing whitespace in the username and password # (assuming whitespace in the password value should be URL encoded if in XML) log local0. "username trimmed: [string trim [findstr $xml_str "" 10 "<"]]" log local0. "password trimmed: [string trim [findstr $xml_str "" 10 "<"]]" }
Log output:
< RULE_INIT>: username raw: my_user < RULE_INIT>: password raw: my_pass < RULE_INIT>: username trimmed: my_user < RULE_INIT>: password trimmed: my_pass
And maybe it would be good to unset username instead of setting it to a null string if you're checking in HTTP_REQUEST to see if it's defined at all? This example would log that $username is defined:
set username "" if {info exists username}{log local0. "\$username is defined"}
Code :
when HTTP_REQUEST { if { ([HTTP::header User-Agent] contains "PNAMain") or ([HTTP::header User-Agent] contains "CitrixReceiver") or ([HTTP::header User-Agent] contains "Dazzle") } { HTTP::header insert "clientless-mode" 1 HTTP::header insert "username" "" HTTP::header insert "password" "" if { [HTTP::cookie exists MRHSession] } { return } if { [HTTP::path] starts_with "/Citrix/PNAgent/" } { set config_URL "/Citrix/PNAgent/config.xml" if { [HTTP::path] equals $config_URL } { ACCESS::disable return } if { ![info exists username] } { # capture the request if { [HTTP::header exists Content-Length] } { HTTP::collect [HTTP::header Content-Length] } } else { HTTP::header replace "username" $username HTTP::header replace "password" $password } } } } when ACCESS_SESSION_STARTED { if { [info exists username] } { ACCESS::session data set session.logon.last.username "$username" ACCESS::session data set session.logon.last.password "$password" } } when HTTP_REQUEST_DATA { set payload [HTTP::payload] set username "" set password "" regexp -nocase {([^<]+) } $payload dummy username regexp -nocase {]+>([^<]+) } $payload dummy password binary scan $password c* pass set len [llength $pass] set result {} for { set i 0 } { $i < $len } { incr i } { set hi [lindex $pass $i] set hi [ expr { $hi - 0x41 } ] set hi [ expr { $hi << 4 } ] incr i set lo [lindex $pass $i] set lo [ expr { $lo - 0x41 } ] set char [ binary format c [expr {$hi + $lo}] ] append result $char } binary scan $result H* pass binary scan $result c* pass set len [llength $pass] set result {} set first [lindex $pass 0] set char [ binary format c [expr { $first ^ 0xA5 } ] ] append result $char for { set i 1 } { $i < $len } { incr i } { set prev [ lindex $pass [expr {$i-1}] ] set curr [ lindex $pass $i ] set char [ binary format c [ expr {$curr ^ $prev ^ 0xA5} ] ] append result $char } binary scan $result H* pass set password [ regsub -all {\000} $result {} ] HTTP::header replace "username" $username HTTP::header replace "password" $password HTTP::release }