# normalize MAC addresses
mac-addr-regexp = '([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})'
# packetfence-local-auth
# Try to get the Cleartext-Password from any of the pfguest, pfsponsor or pfsms
# databases.
# If this fails, the mschap module will try to authenticate using ntlm_auth.

packetfence-local-auth { 
    # Disable ntlm_auth
    update control {
        &MS-CHAP-Use-NTLM-Auth := No
    }
    # Check password table for local user
    pflocal
    if (fail || notfound || noop) {
        # Check password table with email and password for a sponsor registration
        pfguest
        if (fail || notfound || noop) {
            # Check password table with email and password for a guest registration
            pfsponsor
            if (fail || notfound || noop) {
                # Check activation table with phone number and PIN code 
                pfsms
                if (fail || notfound || noop) {
                    update control {
                       &MS-CHAP-Use-NTLM-Auth := Yes
                    }
                }
            }
        }
    }

}

#Set the realm if it's machine authentication
packetfence-set-realm-if-machine {
    if (User-Name =~ /host\/([a-z0-9_-]*)[\.](.*)/i) {
        update {
            &request:Realm := "%{2}"
        }
    }
}

### END Local SQL authentication
packetfence-switch-access { 
    if ( \
        ( &Service-Type ==  "NAS-Prompt-User") && \
        ( &NAS-Port-Type == "Virtual" || &NAS-Port-Type == "Async") ) {
            rest-switch-access  
    }
    
}

request-timing {
    if ("%{%{control:PacketFence-Request-Time}:-0}" != 0) {
        update control {
            &PacketFence-Request-Time := "%{expr: %{control:PacketFence-Request-Time} - %{control:Tmp-Integer-0}}"
        }
    }
}

packetfence-eap-mac-policy {
    if ( &EAP-Type ) {
        if (&User-Name && (&User-Name =~ /^${policy.mac-addr-regexp}$/i)) {
            update {
                &request:Tmp-String-2 := "%{tolower:%{1}%{2}%{3}%{4}%{5}%{6}}"
            }
            if (&Calling-Station-Id && (&Calling-Station-Id =~ /^${policy.mac-addr-regexp}$/i)) {
                update {
                    &request:Tmp-String-1 := "%{tolower:%{1}%{2}%{3}%{4}%{5}%{6}}"
                }
                if  ( &Tmp-String-1 == &Tmp-String-2 ) {
                    update {
                        &control:Cleartext-Password := &request:User-Name
                    }
                    updated
                }
            }
        }

    }
    noop
}


packetfence-audit-log-accept {
    if (&User-Name && (&User-Name == "dummy")) {
        noop
    }
    else {
        request-timing
        %{redis:RPUSH RADIUS_AUDIT_LOG '%{base64:["Accept",%{json_encode:&request:[*]},%{json_encode:&reply:[*]},%{json_encode:control:PacketFence-Computer-Name control:PacketFence-Switch-Id control:PacketFence-Switch-Mac control:PacketFence-Switch-Ip-Address control:PacketFence-IfIndex control:PacketFence-Connection-Type control:Auth-Type control:PacketFence-Role control:PacketFence-Status control:PacketFence-Profile control:PacketFence-Source control:PacketFence-AutoReg control:PacketFence-IsPhone control:PacketFence-Request-Time Packet-Src-IP-Address}]}'}
#       -sql {
#           fail = 1
#       }
    }
}

packetfence-audit-log-reject {
    if (&User-Name && (&User-Name == "dummy")) {
            noop
    }
    else {
        request-timing
        %{redis:RPUSH RADIUS_AUDIT_LOG '%{base64:["Reject",%{json_encode:&request:[*]},%{json_encode:&reply:[*]},%{json_encode:control:PacketFence-Computer-Name control:PacketFence-Switch-Id control:PacketFence-Switch-Mac control:PacketFence-Switch-Ip-Address control:PacketFence-IfIndex control:PacketFence-Connection-Type control:Auth-Type control:PacketFence-Role control:PacketFence-Status control:PacketFence-Profile control:PacketFence-Source control:PacketFence-AutoReg control:PacketFence-IsPhone control:PacketFence-Request-Time Packet-Src-IP-Address}]}'}
#       -sql_reject {
#           fail = 1
#       }
    }
}

packetfence-mschap-authenticate {
    if(PacketFence-Domain) {
      if ( "%{User-Name}" =~ /^host\/.*/) {
        chrooted_mschap_machine
      }
      else {
        chrooted_mschap
      }
    }
    else {
      if ( "%{User-Name}" =~ /^host\/.*/) {
        mschap_machine
      }
      else {
        mschap
      }
    }
}


packetfence-allied-gs950-mab {
    if ( &EAP-Type ) {
        if (&User-Name && (&User-Name =~ /^${policy.mac-addr-regexp}$/i)) {
            update {
                &request:Tmp-String-1 := "%{tolower:%{1}%{2}%{3}%{4}%{5}%{6}}"
            }
            if  ( &Tmp-String-1 == "%{tolower:%{User-Name}}" ) {
                update {
                    &control:Cleartext-Password := &request:User-Name
                    &request:Calling-Station-Id := &request:User-Name
                    &request:NAS-Port-Type := "Ethernet"
                }
                updated
            }
        }
    }
    noop
}

packetfence-cache-ntlm-hit {
  update control {
    Cache-Status-Only = 'yes'
  }
  cache_ntlm
  if (ok) {
    cache_ntlm
    update {
      &request:Tmp-Integer-9 := "%{expr: 1 + %{&request:Tmp-Integer-9}}"
    }
    update control {
      Cache-TTL = 0
    }
    cache_ntlm
    update control {
      #Default TTL to 5 minutes
      Cache-TTL = 300
    }
    cache_ntlm
  }
  else {
    update {
      &request:Tmp-Integer-9 := 0
    }
    cache_ntlm
  }
}

packetfence-control-ntlm-failure {
  update control {
    Cache-Status-Only = 'yes'
  }
  cache_ntlm
  if (ok) {
    cache_ntlm
    # raise the value if you want to permit more ntlm_auth failure before rejecting the request
    if (&request:Tmp-Integer-9 > 1) {
      reject
    }
  }
}

last-regexp = '(.*)(.)'

packetfence-balanced-key-policy {
    if (&PacketFence-KeyBalanced && (&PacketFence-KeyBalanced =~ /^${policy.last-regexp}$/i)) {
        update {
            &request:PacketFence-KeyBalanced := "%{md5:%{2}%{1}}"
            &control:Load-Balance-Key := "%{md5:%{2}%{1}}"
        }
    }
    else {
        update {
            &request:PacketFence-KeyBalanced := "%{md5:%{Calling-Station-Id}%{User-Name}}"
            &control:Load-Balance-Key := "%{md5:%{Calling-Station-Id}%{User-Name}}"
        }
    }
}

packetfence-nas-ip-address {
    if(!NAS-IP-Address || NAS-IP-Address == "0.0.0.0"){
            update request {
                    NAS-IP-Address := "%{Packet-Src-IP-Address}"
            }
    }
}

packetfence-degraded-auth {
    # Disable ntlm_auth
    update control {
        &MS-CHAP-Use-NTLM-Auth := No
    }
    # Check password table for local user
    sql_degraded
    if (fail || notfound || noop) {
        update control {
            &MS-CHAP-Use-NTLM-Auth := Yes
        }
    }
}

packetfence-base64-password {
    if (&User-Password) {
        update {
            request:PacketFence-UserPassword := "%{base64: %{User-Password}}"
        }
    }
}

#
#  Audit row for a request that was deliberately left unanswered (see
#  packetfence-post-auth-rest): keeps an httpd.aaa outage visible in the
#  RADIUS audit log instead of a gap.
#
packetfence-audit-log-no-response {
    if (&User-Name && (&User-Name == "dummy")) {
        noop
    }
    else {
        #  rlm_rest does not process the body of a 5xx answer ("Process body no",
        #  see the status code table in conf/radiusd/rest.conf), so none of the
        #  control:PacketFence-* pairs that the Accept and Reject rows carry are
        #  set on this path. Most of them cannot be: role, status, profile,
        #  source, ifIndex and connection type are what PacketFence decided, and
        #  here it never evaluated the request. Fill in the two radiusd knows
        #  first-hand so the row is not emptier than it has to be; the rest are
        #  left to the consumer's N/A default rather than guessed at.
        update control {
            #  Absolute timestamp, the same shape PacketFence returns on success;
            #  request-timing below turns it into the elapsed time.
            &PacketFence-Request-Time := "%l"
            #  packetfence-nas-ip-address has already guaranteed this is set
            #  (Packet-Src-IP-Address when the NAS omits it), and it is the
            #  address PacketFence itself keys the switch lookup on.
            &PacketFence-Switch-Ip-Address := "%{request:NAS-IP-Address}"
        }
        request-timing
        %{redis:RPUSH RADIUS_AUDIT_LOG '%{base64:["NoResponse",%{json_encode:&request:[*]},%{json_encode:&reply:[*]},%{json_encode:control:PacketFence-Computer-Name control:PacketFence-Switch-Id control:PacketFence-Switch-Mac control:PacketFence-Switch-Ip-Address control:PacketFence-IfIndex control:PacketFence-Connection-Type control:Auth-Type control:PacketFence-Role control:PacketFence-Status control:PacketFence-Profile control:PacketFence-Source control:PacketFence-AutoReg control:PacketFence-IsPhone control:PacketFence-Request-Time Packet-Src-IP-Address}]}'}
    }
}

#
#  Post-auth call to httpd.aaa (/radius/rest/authorize) with a failure policy.
#
#  rlm_rest returns "fail" when httpd.aaa is unreachable, times out or answers
#  5xx (which /radius/rest/authorize does for infrastructure failures such as
#  an unavailable database). Letting the section fail sent an Access-Reject,
#  which a NAS treats as authoritative: on a periodic reauthentication it tears
#  down a session it had already authorized. Every httpd.aaa restart did that
#  to thousands of devices.
#
#  Everything without EAP is left unanswered (do_not_respond). The NAS
#  retransmits and, if PacketFence stays silent long enough, marks the server
#  dead and applies its own fail-over policy (critical-auth on Cisco); the
#  existing session is untouched.
#
#  That is what MAB and PAP need: they are what a switch reauthenticates a live
#  session with, and a Reject there drops a port that was already authorized.
#  It also catches direct Auth-Type CHAP and MS-CHAP (packetfence.example,
#  Auth-Type sections), which is intentional: with a non-Wireless/Ethernet
#  NAS-Port-Type those are interactive CLI and VPN logins, one-shot rather than
#  a reauthentication of an established session, so nothing is torn down either
#  way and the person simply retries -- they are already waiting on a prompt.
#
#  EAP: the Reject is kept. rlm_eap has already freed the conversation when
#  post-auth runs, so a retransmit cannot be resumed; silence would only delay
#  the Reject while getting the server marked dead. Deliberate denials
#  (PacketFence-Authorization-Status = deny, 401) are unaffected either way.
#
packetfence-post-auth-rest {
    rest {
        fail = 1
    }
    if (fail) {
        update {
            &request:User-Password := "******"
        }
        if (&EAP-Type) {
            reject
        }
        else {
            packetfence-audit-log-no-response
            do_not_respond
        }
    }
    update {
        &request:User-Password := "******"
    }
}
