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
}
Published Mar 18, 2015
Version 1.0
No CommentsBe the first to comment