cancel
Showing results for 
Search instead for 
Did you mean: 
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. The following iRule provides the functionality for a secure proxy connection from various Citrix clients (PN Agent, Dazzle, Receiver and Web Browser) without the need for additional clients installed on the devices.

How to use this snippet:

Deployment Guide: https://f5.com/solutions/deployment-guides/citrix-xenapp-or-xendesktop-release-candidate-big

Code :

rule APM_Citrix {

    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:3: Request [HTTP::request]"

        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:3: Request for citrix resource received from session: $apm_session"
            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:3: Parsing credentials for Citrix receiver" 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:3: Parsing credentials for Citrix PNAgent" 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:3: $tmm_apm_session_id: Username not found in the PNAgent POST body" return } if { [regexp -nocase {]+>([^<]+)} $payload dummy tmm_apm_citrix_password] == 0 } { log -noname accesscontrol.local1.error "01490000:3: $tmm_apm_session_id: Password not found in the PNAgent POST body" 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:3: ICA file patching" 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:
‎30-Jan-2015 07:18
Updated by:
Contributors