cancel
Showing results for 
Search instead for 
Did you mean: 
Login & Join the DevCentral Connects Group to watch the Recorded LiveStream (May 12) on Basic iControl Security - show notes included.
Nojan_Moshiri_4
Historic F5 Account

Problem this snippet solves:

With the combination of BIG-IP Access Policy Manager (APM) and Citrix XenApp, organizations can deliver a complete remote access solution that allows for scalability, security, compliance and flexibility.

This iRule comprises the first part of two iRules that are part of the Citrix Secure Access deployment. The complete installation instructions for this setup will be located in the solutions section of F5.com shortly.

With this iRule, users can connect using BIG-IP APM's secure proxy mode, allowing for connections to XenApp from a variety of clients including Citrix Receiver, PN Agent and Dazzle.

Code :

when RULE_INIT {
        set tmm_apm_pnagent_url "/Citrix/PNAgent/config.xml"
    }

    when CLIENT_ACCEPTED {
        TCP::collect 7
    }

    when CLIENT_DATA {
        # Disable SSL if it's HTTP CONNECT request
        if { [TCP::payload 7] equals "CONNECT" } {
            SSL::disable
        }
        TCP::release
    }

    when HTTP_REQUEST {
        set tmm_apm_host [HTTP::host]
        set tmm_apm_uri_path [HTTP::path]
        set tmm_apm_user_agent [HTTP::header "User-Agent"]
        set tmm_apm_http_method [HTTP::method]
        set tmm_apm_session_id ""
        set tmm_apm_citrix_receiver 0
        set tmm_apm_citrix_pnagent  0
        set tmm_apm_citrix_ica_patching  0
        set tmm_apm_vip  "$tmm_apm_host:[TCP::local_port clientside]"

        log -noname accesscontrol.local1.debug "01490000

        if { [HTTP::cookie exists "MRHSession"] } {
            set tmm_apm_session_id [HTTP::cookie "MRHSession"]
        }

        if { $tmm_apm_user_agent contains "CitrixReceiver" } {
            set tmm_apm_citrix_receiver 1
        } elseif { $tmm_apm_user_agent contains "PNAMAIN" or $tmm_apm_user_agent contains "Dazzle" } {
            set tmm_apm_citrix_pnagent  1
        }

        if { $tmm_apm_http_method equals "CONNECT" } {

            # Handle the secure proxy connect requests. Return a Proxy-Authenticate header
            # field with a challenge if the user is not authenticated.

            if { ![HTTP::header exists "Proxy-Authorization"] } {
                HTTP::respond 407 Proxy-Authenticate "Basic realm=\"123\""
                return
            }

            set authstr [lindex [ split [HTTP::header "Proxy-Authorization"] " " ] 1 ]

            # Seems like the Citrix base64 encoding logic has a bug that terminates
            # the input string with a null byte when the extra padding characters are
            # added. We remove the extra null character before we decode it.  
            set remainder [lindex [split [expr [string length $authstr] / 4.0 ] "." ] 1]
            if { $remainder != "0" } {
                if { [regsub -all {(A=)} $authstr = newstring] > 0 } {
                    set authstr $newstring
                }
            }

            #Decoded string format: 52553eb5b18572cdbe7dda4a8220bf35:172.30.6.197-1494
            set apm_session [ lindex [ split [b64decode $authstr] ":" ] 0 ]

            if { ![ACCESS::session exists $apm_session] } {
                HTTP::respond 407 Proxy-Authenticate "Basic realm=\"123\""
                return
            }

            # User is authenticated, send the traffic to the connect proxy virtual.
            log -noname accesscontrol.local1.notice "01490000
            ACCESS::disable
            use virtual citrix_connect_proxy
        }

        if { ($tmm_apm_session_id == "") && ($tmm_apm_citrix_pnagent == 1) } {

            if { $tmm_apm_uri_path equals $::tmm_apm_pnagent_url } {
                ACCESS::disable
                return
            }

            # If the client is PNAgent or Dazzle, extract the credentials from the
            # payload and insert them in HTTP headers.

            HTTP::header insert "clientless-mode" 1
            HTTP::header insert "username" ""
            HTTP::header insert "password" ""

            if { ![info exists tmm_apm_citrix_username] && [HTTP::header exists Content-Length] } {
                HTTP::collect [HTTP::header Content-Length]
            }
        }

        if { $tmm_apm_citrix_receiver == 1 } {

            # Collect the user credentials and set ready for access policy validation
            if { $tmm_apm_uri_path equals "/cgi/login" } {
                HTTP::header insert "clientless-mode" 1
                HTTP::header insert "username" ""
                HTTP::header insert "password" ""
                HTTP::cookie remove MRHSession
                HTTP::collect [HTTP::header Content-Length]
            } elseif { $tmm_apm_uri_path equals "/ipad" } {
                set AD_only "citrixreceiver://createprofile/?s=$tmm_apm_host&pname=Profile-$tmm_apm_host&gw=1&gwt=2&gwa=1"
                set RSA_only "citrixreceiver://createprofile/?s=$tmm_apm_host&pname=Profile-$tmm_apm_host&gw=1&gwt=2&gwa=2"
                set AD_RSA "citrixreceiver://createprofile/?s=$tmm_apm_host&pname=Profile-$tmm_apm_host&gw=1&gwt=2&gwa=3"
                HTTP::respond 200 content "

Click here for domain only authClick here for RSA onlyClick here for Two-factor auth

" } } } when HTTP_REQUEST_DATA { if { ($tmm_apm_citrix_pnagent != 1) && ($tmm_apm_citrix_receiver != 1) } { return } set payload [HTTP::payload] if { $tmm_apm_citrix_receiver == 1 } { # Parse the user credentials from the payload log -noname accesscontrol.local1.debug "01490000 set tmm_apm_citrix_username "" set tmm_apm_citrix_password "" set tmm_apm_citrix_password1 "" set urlvars [ split $payload "&" ] foreach {u} $urlvars { set param [ lindex [ split $u "=" ] 0 ] set value [ lindex [ split $u "=" ] 1 ] if { $param equals "login" } { set tmm_apm_citrix_username $value } elseif { $param equals "passwd" } { set tmm_apm_citrix_password $value } elseif { $param equals "passwd1" } { set tmm_apm_citrix_password1 $value } } # Insert the parsed credentials into the HTTP request as headers HTTP::header replace "username" $tmm_apm_citrix_username HTTP::header replace "password" $tmm_apm_citrix_password HTTP::release } elseif { $tmm_apm_citrix_pnagent == 1 } { # Parse the user credentials from the payload log -noname accesscontrol.local1.debug "01490000 set tmm_apm_citrix_username "" set tmm_apm_citrix_password "" if { [regexp -nocase {([^<]+)} $payload dummy tmm_apm_citrix_username] == 0 } { log -noname accesscontrol.local1.error "01490000 return } if { [regexp -nocase {]+>([^<]+)} $payload dummy tmm_apm_citrix_password] == 0 } { log -noname accesscontrol.local1.error "01490000 return } # Decode the password binary scan $tmm_apm_citrix_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 tmm_apm_citrix_password [ regsub -all {\000} $result {} ] # Insert the parsed credentials into the HTTP request as headers HTTP::header replace "username" $tmm_apm_citrix_username HTTP::header replace "password" $tmm_apm_citrix_password HTTP::release } } when HTTP_RESPONSE { if { [HTTP::header Content-Type] contains "application/x-ica" } { set tmm_apm_citrix_ica_patching 1 HTTP::collect [HTTP::header Content-Length] } } when HTTP_RESPONSE_DATA { # ICA patching: if { $tmm_apm_citrix_ica_patching == 1 } { # ICA file patching: Add entries to point citrix clients to the # Citrix ICA patching virtual as their HTTP proxy. It also sets # the ProxyUsername to the APM session id to let the Citrix clients # to connect to the proxy without requesting the user to authenticate # again. log -noname accesscontrol.local1.debug "01490000 set payload [HTTP::payload] set payload [ regsub -all {Proxy[^\n]+\n} $payload {} ] set payload [ regsub {DoNotUseDefaultCSL[^\n]+\n} $payload {} ] if { $tmm_apm_citrix_receiver == 1 } { set payload [ regsub {CGPAddress[^\n]+\n} $payload {} ] } regexp -line {Address=(.+)} $payload dummy CtxAddrPort set CtxAddr [lindex [split $CtxAddrPort ":"] 0] set CtxPort [lindex [split $CtxAddrPort ":"] 1] regexp -line {CGPAddress=(.+)} $payload dummy CGPAddrPort if { [info exists CGPAddrPort] } { set CtxPort [lindex [split $CGPAddrPort ":"] 1] } set payload [ regsub {\[WFClient\]} $payload "&\r\nProxyType=Secure\r\nProxyHost=$tmm_apm_vip\r\nProxyUsername=$tmm_apm_session_id\r\nProxyPassword=$CtxAddr-$CtxPort" ] set payload [ regsub {SSLEnable[^\n]+\n} $payload "SSLEnable=On\r\n" ] set payload [ regsub {Address[^\n]+\n} $payload "Address=$tmm_apm_host\r\n" ] HTTP::respond 200 content $payload Content-Type [HTTP::header Content-Type] } } when ACCESS_SESSION_STARTED { if { ($tmm_apm_citrix_receiver == 0) or ![info exists tmm_apm_citrix_password1] } { return } # Pass the domain password as a session variable. Logon page agent doesn't # take it from HTTP headers in clientless mode. ACCESS::session data set "session.logon.last.password1" [URI::decode $tmm_apm_citrix_password1] } when ACCESS_POLICY_COMPLETED { if { $tmm_apm_citrix_receiver == 0 } { return } set sid [ACCESS::session data get session.keydb] set result [ACCESS::policy result] # Remove the user credential variables if { [info exists tmm_apm_citrix_username] } { unset tmm_apm_citrix_username } if { [info exists tmm_apm_citrix_password] } { unset tmm_apm_citrix_password } if { [info exists tmm_apm_citrix_password1] } { unset tmm_apm_citrix_password1 } # Clear the domain password session variable created at the session validation start. ACCESS::session data set "session.logon.last.password1" "" if { $result equals "allow" } { set resp "" ACCESS::respond 200 content $resp Set-Cookie "MRHSession=$sid;path=/;secure" Set-Cookie "NSC_AAAC=123;path=/;secure" } }
Version history
Last update:
‎16-Mar-2015 15:59
Updated by:
Contributors