# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
# Acknowledgment: Vincent Mauge <vmauge.nospam@nospam.gmail.com>

"""
RADIUS (Remote Authentication Dial In User Service)

To disable Radius-EAP defragmentation (True by default), you can use::

    conf.contribs.setdefault("radius", {}).setdefault("auto-defrag", False)
"""

import struct
import hashlib
import hmac
from scapy.compat import orb, raw
from scapy.packet import Packet, Padding, bind_layers, bind_bottom_up
from scapy.fields import ByteField, ByteEnumField, IntField, StrLenField,\
    XStrLenField, XStrFixedLenField, FieldLenField, PacketLenField,\
    PacketListField, IPField, MultiEnumField
from scapy.layers.inet import UDP
from scapy.layers.eap import EAP
from scapy.config import conf
from scapy.error import Scapy_Exception

# https://www.iana.org/assignments/radius-types/radius-types.xhtml
_radius_attribute_types = {
    1: "User-Name",
    2: "User-Password",
    3: "CHAP-Password",
    4: "NAS-IP-Address",
    5: "NAS-Port",
    6: "Service-Type",
    7: "Framed-Protocol",
    8: "Framed-IP-Address",
    9: "Framed-IP-Netmask",
    10: "Framed-Routing",
    11: "Filter-Id",
    12: "Framed-MTU",
    13: "Framed-Compression",
    14: "Login-IP-Host",
    15: "Login-Service",
    16: "Login-TCP-Port",
    17: "Unassigned",
    18: "Reply-Message",
    19: "Callback-Number",
    20: "Callback-Id",
    21: "Unassigned",
    22: "Framed-Route",
    23: "Framed-IPX-Network",
    24: "State",
    25: "Class",
    26: "Vendor-Specific",
    27: "Session-Timeout",
    28: "Idle-Timeout",
    29: "Termination-Action",
    30: "Called-Station-Id",
    31: "Calling-Station-Id",
    32: "NAS-Identifier",
    33: "Proxy-State",
    34: "Login-LAT-Service",
    35: "Login-LAT-Node",
    36: "Login-LAT-Group",
    37: "Framed-AppleTalk-Link",
    38: "Framed-AppleTalk-Network",
    39: "Framed-AppleTalk-Zone",
    40: "Acct-Status-Type",
    41: "Acct-Delay-Time",
    42: "Acct-Input-Octets",
    43: "Acct-Output-Octets",
    44: "Acct-Session-Id",
    45: "Acct-Authentic",
    46: "Acct-Session-Time",
    47: "Acct-Input-Packets",
    48: "Acct-Output-Packets",
    49: "Acct-Terminate-Cause",
    50: "Acct-Multi-Session-Id",
    51: "Acct-Link-Count",
    52: "Acct-Input-Gigawords",
    53: "Acct-Output-Gigawords",
    54: "Unassigned",
    55: "Event-Timestamp",
    56: "Egress-VLANID",
    57: "Ingress-Filters",
    58: "Egress-VLAN-Name",
    59: "User-Priority-Table",
    60: "CHAP-Challenge",
    61: "NAS-Port-Type",
    62: "Port-Limit",
    63: "Login-LAT-Port",
    64: "Tunnel-Type",
    65: "Tunnel-Medium-Type",
    66: "Tunnel-Client-Endpoint",
    67: "Tunnel-Server-Endpoint",
    68: "Acct-Tunnel-Connection",
    69: "Tunnel-Password",
    70: "ARAP-Password",
    71: "ARAP-Features",
    72: "ARAP-Zone-Access",
    73: "ARAP-Security",
    74: "ARAP-Security-Data",
    75: "Password-Retry",
    76: "Prompt",
    77: "Connect-Info",
    78: "Configuration-Token",
    79: "EAP-Message",
    80: "Message-Authenticator",
    81: "Tunnel-Private-Group-ID",
    82: "Tunnel-Assignment-ID",
    83: "Tunnel-Preference",
    84: "ARAP-Challenge-Response",
    85: "Acct-Interim-Interval",
    86: "Acct-Tunnel-Packets-Lost",
    87: "NAS-Port-Id",
    88: "Framed-Pool",
    89: "CUI",
    90: "Tunnel-Client-Auth-ID",
    91: "Tunnel-Server-Auth-ID",
    92: "NAS-Filter-Rule",
    93: "Unassigned",
    94: "Originating-Line-Info",
    95: "NAS-IPv6-Address",
    96: "Framed-Interface-Id",
    97: "Framed-IPv6-Prefix",
    98: "Login-IPv6-Host",
    99: "Framed-IPv6-Route",
    100: "Framed-IPv6-Pool",
    101: "Error-Cause",
    102: "EAP-Key-Name",
    103: "Digest-Response",
    104: "Digest-Realm",
    105: "Digest-Nonce",
    106: "Digest-Response-Auth",
    107: "Digest-Nextnonce",
    108: "Digest-Method",
    109: "Digest-URI",
    110: "Digest-Qop",
    111: "Digest-Algorithm",
    112: "Digest-Entity-Body-Hash",
    113: "Digest-CNonce",
    114: "Digest-Nonce-Count",
    115: "Digest-Username",
    116: "Digest-Opaque",
    117: "Digest-Auth-Param",
    118: "Digest-AKA-Auts",
    119: "Digest-Domain",
    120: "Digest-Stale",
    121: "Digest-HA1",
    122: "SIP-AOR",
    123: "Delegated-IPv6-Prefix",
    124: "MIP6-Feature-Vector",
    125: "MIP6-Home-Link-Prefix",
    126: "Operator-Name",
    127: "Location-Information",
    128: "Location-Data",
    129: "Basic-Location-Policy-Rules",
    130: "Extended-Location-Policy-Rules",
    131: "Location-Capable",
    132: "Requested-Location-Info",
    133: "Framed-Management-Protocol",
    134: "Management-Transport-Protection",
    135: "Management-Policy-Id",
    136: "Management-Privilege-Level",
    137: "PKM-SS-Cert",
    138: "PKM-CA-Cert",
    139: "PKM-Config-Settings",
    140: "PKM-Cryptosuite-List",
    141: "PKM-SAID",
    142: "PKM-SA-Descriptor",
    143: "PKM-Auth-Key",
    144: "DS-Lite-Tunnel-Name",
    145: "Mobile-Node-Identifier",
    146: "Service-Selection",
    147: "PMIP6-Home-LMA-IPv6-Address",
    148: "PMIP6-Visited-LMA-IPv6-Address",
    149: "PMIP6-Home-LMA-IPv4-Address",
    150: "PMIP6-Visited-LMA-IPv4-Address",
    151: "PMIP6-Home-HN-Prefix",
    152: "PMIP6-Visited-HN-Prefix",
    153: "PMIP6-Home-Interface-ID",
    154: "PMIP6-Visited-Interface-ID",
    155: "PMIP6-Home-IPv4-HoA",
    156: "PMIP6-Visited-IPv4-HoA",
    157: "PMIP6-Home-DHCP4-Server-Address",
    158: "PMIP6-Visited-DHCP4-Server-Address",
    159: "PMIP6-Home-DHCP6-Server-Address",
    160: "PMIP6-Visited-DHCP6-Server-Address",
    161: "PMIP6-Home-IPv4-Gateway",
    162: "PMIP6-Visited-IPv4-Gateway",
    163: "EAP-Lower-Layer",
    164: "GSS-Acceptor-Service-Name",
    165: "GSS-Acceptor-Host-Name",
    166: "GSS-Acceptor-Service-Specifics",
    167: "GSS-Acceptor-Realm-Name",
    168: "Framed-IPv6-Address",
    169: "DNS-Server-IPv6-Address",
    170: "Route-IPv6-Information",
    171: "Delegated-IPv6-Prefix-Pool",
    172: "Stateful-IPv6-Address-Pool",
    173: "IPv6-6rd-Configuration",
    174: "Allowed-Called-Station-Id",
    175: "EAP-Peer-Id",
    176: "EAP-Server-Id",
    177: "Mobility-Domain-Id",
    178: "Preauth-Timeout",
    179: "Network-Id-Name",
    180: "EAPoL-Announcement",
    181: "WLAN-HESSID",
    182: "WLAN-Venue-Info",
    183: "WLAN-Venue-Language",
    184: "WLAN-Venue-Name",
    185: "WLAN-Reason-Code",
    186: "WLAN-Pairwise-Cipher",
    187: "WLAN-Group-Cipher",
    188: "WLAN-AKM-Suite",
    189: "WLAN-Group-Mgmt-Cipher",
    190: "WLAN-RF-Band",
    191: "Unassigned",
}


class RadiusAttribute(Packet):
    """
    Implements a RADIUS attribute (RFC 2865). Every specific RADIUS attribute
    class should inherit from this one.
    """

    name = "Radius Attribute"
    fields_desc = [
        ByteEnumField("type", 1, _radius_attribute_types),
        FieldLenField("len", None, "value", "B",
                      adjust=lambda pkt, x: len(pkt.value) + 2),
        StrLenField("value", "", length_from=lambda pkt: pkt.len - 2)
    ]

    registered_attributes = {}

    @classmethod
    def register_variant(cls):
        """
        Registers the RADIUS attributes defined in this module.
        """

        if hasattr(cls, "val"):
            cls.registered_attributes[cls.val] = cls
        else:
            cls.registered_attributes[cls.type.default] = cls

    @classmethod
    def dispatch_hook(cls, _pkt=None, *args, **kargs):
        """
        Returns the right RadiusAttribute class for the given data.
        """

        if _pkt:
            attr_type = orb(_pkt[0])
            return cls.registered_attributes.get(attr_type, cls)
        return cls

    def post_build(self, p, pay):
        length = self.len
        if length is None:
            length = len(p)
            p = p[:1] + struct.pack("!B", length) + p[2:]
        return p

    def guess_payload_class(self, _):
        return Padding


class _SpecificRadiusAttr(RadiusAttribute):
    """
    Class from which every "specific" RADIUS attribute defined in this module
    inherits.
    """

    __slots__ = ["val"]
    match_subclass = True

    def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields):  # noqa: E501
        super(_SpecificRadiusAttr, self).__init__(
            _pkt,
            post_transform,
            _internal,
            _underlayer,
            **fields
        )
        self.fields["type"] = self.val
        name_parts = self.__class__.__name__.split('RadiusAttr_')
        if len(name_parts) < 2:
            raise Scapy_Exception(
                "Invalid class name: {}".format(self.__class__.__name__)
            )
        self.name = name_parts[1].replace('_', '-')


#
# RADIUS attributes which values are 4 bytes integers
#

class _RadiusAttrIntValue(_SpecificRadiusAttr):
    """
    Implements a RADIUS attribute which value field is 4 bytes long integer.
    """

    fields_desc = [
        ByteEnumField("type", 5, _radius_attribute_types),
        ByteField("len", 6),
        IntField("value", 0)
    ]


class RadiusAttr_User_Name(_SpecificRadiusAttr):
    """RFC 2865"""
    val = 1


class RadiusAttr_NAS_Port(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 5


class RadiusAttr_Framed_MTU(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 12


class RadiusAttr_Login_TCP_Port(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 16


class RadiusAttr_Session_Timeout(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 27


class RadiusAttr_Idle_Timeout(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 28


class RadiusAttr_Framed_AppleTalk_Link(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 37


class RadiusAttr_Framed_AppleTalk_Network(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 38


class RadiusAttr_Acct_Delay_Time(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 41


class RadiusAttr_Acct_Input_Octets(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 42


class RadiusAttr_Acct_Output_Octets(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 43


class RadiusAttr_Acct_Session_Time(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 46


class RadiusAttr_Acct_Input_Packets(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 47


class RadiusAttr_Acct_Output_Packets(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 48


class RadiusAttr_Acct_Link_Count(_RadiusAttrIntValue):
    """RFC 2866"""
    val = 51


class RadiusAttr_Acct_Input_Gigawords(_RadiusAttrIntValue):
    """RFC 2869"""
    val = 52


class RadiusAttr_Acct_Output_Gigawords(_RadiusAttrIntValue):
    """RFC 2869"""
    val = 53


class RadiusAttr_Egress_VLANID(_RadiusAttrIntValue):
    """RFC 4675"""
    val = 56


class RadiusAttr_Port_Limit(_RadiusAttrIntValue):
    """RFC 2865"""
    val = 62


class RadiusAttr_ARAP_Security(_RadiusAttrIntValue):
    """RFC 2869"""
    val = 73


class RadiusAttr_Password_Retry(_RadiusAttrIntValue):
    """RFC 2869"""
    val = 75


class RadiusAttr_Tunnel_Preference(_RadiusAttrIntValue):
    """RFC 2868"""
    val = 83


class RadiusAttr_Acct_Interim_Interval(_RadiusAttrIntValue):
    """RFC 2869"""
    val = 85


class RadiusAttr_Acct_Tunnel_Packets_Lost(_RadiusAttrIntValue):
    """RFC 2867"""
    val = 86


class RadiusAttr_Management_Privilege_Level(_RadiusAttrIntValue):
    """RFC 5607"""
    val = 136


class RadiusAttr_Mobility_Domain_Id(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 177


class RadiusAttr_Preauth_Timeout(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 178


class RadiusAttr_WLAN_Venue_Info(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 182


class RadiusAttr_WLAN_Reason_Code(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 185


class RadiusAttr_WLAN_Pairwise_Cipher(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 186


class RadiusAttr_WLAN_Group_Cipher(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 187


class RadiusAttr_WLAN_AKM_Suite(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 188


class RadiusAttr_WLAN_Group_Mgmt_Cipher(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 189


class RadiusAttr_WLAN_RF_Band(_RadiusAttrIntValue):
    """RFC 7268"""
    val = 190


#
# RADIUS attributes which values are string (displayed as hex)
#

class _RadiusAttrHexStringVal(_SpecificRadiusAttr):
    """
    Implements a RADIUS attribute which value field is a string that will be
    as a hex string.
    """

    __slots__ = ["val"]

    def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields):  # noqa: E501
        super(_RadiusAttrHexStringVal, self).__init__(
            _pkt,
            post_transform,
            _internal,
            _underlayer,
            **fields
        )
        self.fields["type"] = self.val
        name_parts = self.__class__.__name__.split('RadiusAttr_')
        if len(name_parts) < 2:
            raise Scapy_Exception(
                "Invalid class name: {}".format(self.__class__.__name__)
            )
        self.name = name_parts[1].replace('_', '-')

    fields_desc = [
        ByteEnumField("type", 24, _radius_attribute_types),
        FieldLenField(
            "len",
            None,
            "value",
            "B",
            adjust=lambda p, x: len(p.value) + 2
        ),
        XStrLenField("value", "", length_from=lambda p: p.len - 2 if p.len else 0)  # noqa: E501
    ]


class RadiusAttr_User_Password(_RadiusAttrHexStringVal):
    """RFC 2865"""
    val = 2


class RadiusAttr_State(_RadiusAttrHexStringVal):
    """RFC 2865"""
    val = 24


def prepare_packed_data(radius_packet, packed_req_authenticator):
    """
    Pack RADIUS data prior computing the authentication MAC
    """

    packed_hdr = struct.pack("!B", radius_packet.code)
    packed_hdr += struct.pack("!B", radius_packet.id)
    packed_hdr += struct.pack("!H", radius_packet.len)

    packed_attrs = b''
    for attr in radius_packet.attributes:
        packed_attrs += raw(attr)

    return packed_hdr + packed_req_authenticator + packed_attrs


class RadiusAttr_Message_Authenticator(_RadiusAttrHexStringVal):
    """RFC 2869"""
    val = 80

    fields_desc = [
        ByteEnumField("type", 24, _radius_attribute_types),
        FieldLenField(
            "len",
            18,
            "value",
            "B",
        ),
        XStrFixedLenField("value", "\x00" * 16, length=16)
    ]

    @staticmethod
    def compute_message_authenticator(radius_packet, packed_req_authenticator,
                                      shared_secret):
        """
        Computes the "Message-Authenticator" of a given RADIUS packet.
        (RFC 2869 - Page 33)
        """

        attr = radius_packet[RadiusAttr_Message_Authenticator]
        attr.value = bytearray(attr.len - 2)
        data = prepare_packed_data(radius_packet, packed_req_authenticator)
        radius_hmac = hmac.new(shared_secret, data, hashlib.md5)

        return radius_hmac.digest()

#
# RADIUS attributes which values are IPv4 prefixes
#


class _RadiusAttrIPv4AddrVal(_SpecificRadiusAttr):
    """
    Implements a RADIUS attribute which value field is an IPv4 address.
    """

    __slots__ = ["val"]

    fields_desc = [
        ByteEnumField("type", 4, _radius_attribute_types),
        ByteField("len", 6),
        IPField("value", "0.0.0.0")
    ]


class RadiusAttr_NAS_IP_Address(_RadiusAttrIPv4AddrVal):
    """RFC 2865"""
    val = 4


class RadiusAttr_Framed_IP_Address(_RadiusAttrIPv4AddrVal):
    """RFC 2865"""
    val = 8


class RadiusAttr_Framed_IP_Netmask(_RadiusAttrIPv4AddrVal):
    """RFC 2865"""
    val = 9


class RadiusAttr_Login_IP_Host(_RadiusAttrIPv4AddrVal):
    """RFC 2865"""
    val = 14


class RadiusAttr_Framed_IPX_Network(_RadiusAttrIPv4AddrVal):
    """RFC 2865"""
    val = 23


class RadiusAttr_PMIP6_Home_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 149


class RadiusAttr_PMIP6_Visited_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 150


class RadiusAttr_PMIP6_Home_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 157


class RadiusAttr_PMIP6_Visited_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 158


class RadiusAttr_PMIP6_Home_IPv4_Gateway(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 161


class RadiusAttr_PMIP6_Visited_IPv4_Gateway(_RadiusAttrIPv4AddrVal):
    """RFC 6572"""
    val = 162


# See IANA registry "RADIUS Types"
_radius_attrs_values = {
    # Service-Type
    6:
    {
        1: "Login",
        2: "Framed",
        3: "Callback Login",
        4: "Callback Framed",
        5: "Outbound",
        6: "Administrative",
        7: "NAS Prompt",
        8: "Authenticate Only",
        9: "Callback NAS Prompt",
        10: "Call Check",
        11: "Callback Administrative",
        12: "Voice",
        13: "Fax",
        14: "Modem Relay",
        15: "IAPP-Register",
        16: "IAPP-AP-Check",
        17: "Authorize Only",
        18: "Framed-Management",
        19: "Additional-Authorization"
    },

    # Framed-Protocol
    7:
    {
        1: "PPP",
        2: "SLIP",
        3: "AppleTalk Remote Access Protocol (ARAP)",
        4: "Gandalf proprietary SingleLink/MultiLink protocol",
        5: "Xylogics proprietary IPX/SLIP",
        6: "X.75 Synchronous",
        7: "GPRS PDP Context"
    },

    # Framed-Routing
    10:
    {
        0: "None",
        1: "Send routing packets",
        2: "Listen for routing packets",
        3: "Send and Listen"
    },

    # Framed-Compression
    13:
    {
        0: "None",
        1: "VJ TCP/IP header compression",
        2: "IPX header compression",
        3: "Stac-LZS compression"
    },

    # Login-Service
    15:
    {
        0: "Telnet",
        1: "Rlogin",
        2: "TCP Clear",
        3: "PortMaster (proprietary)",
        4: "LAT",
        5: "X25-PAD",
        6: "X25-T3POS",
        7: "Unassigned",
        8: "TCP Clear Quiet (suppresses any NAS-generated connect string)"
    },

    # Termination-Action
    29:
    {
        0: "Default",
        1: "RADIUS-Request"
    },

    # Acct-Status-Type
    40:
    {
        1: "Start",
        2: "Stop",
        3: "Interim-Update",
        4: "Unassigned",
        5: "Unassigned",
        6: "Unassigned",
        7: "Accounting-On",
        8: "Accounting-Off",
        9: "Tunnel-Start",
        10: "Tunnel-Stop",
        11: "Tunnel-Reject",
        12: "Tunnel-Link-Start",
        13: "Tunnel-Link-Stop",
        14: "Tunnel-Link-Reject",
        15: "Failed"
    },

    # Acct-Authentic
    45:
    {
        1: "RADIUS",
        2: "Local",
        3: "Remote",
        4: "Diameter"
    },

    # Acct-Terminate-Cause
    49:
    {
        1: "User Request",
        2: "Lost Carrier",
        3: "Lost Service",
        4: "Idle Timeout",
        5: "Session Timeout",
        6: "Admin Reset",
        7: "Admin Reboot",
        8: "Port Error",
        9: "NAS Error",
        10: "NAS Request",
        11: "NAS Reboot",
        12: "Port Unneeded",
        13: "Port Preempted",
        14: "Port Suspended",
        15: "Service Unavailable",
        16: "Callback",
        17: "User Error",
        18: "Host Request",
        19: "Supplicant Restart",
        20: "Reauthentication Failure",
        21: "Port Reinitialized",
        22: "Port Administratively Disabled",
        23: "Lost Power",
    },

    # NAS-Port-Type
    61:
    {
        0: "Async",
        1: "Sync",
        2: "ISDN Sync",
        3: "ISDN Async V.120",
        4: "ISDN Async V.110",
        5: "Virtual",
        6: "PIAFS",
        7: "HDLC Clear Channel",
        8: "X.25",
        9: "X.75",
        10: "G.3 Fax",
        11: "SDSL - Symmetric DSL",
        12: "ADSL-CAP - Asymmetric DSL, Carrierless Amplitude Phase Modulation",  # noqa: E501
        13: "ADSL-DMT - Asymmetric DSL, Discrete Multi-Tone",
        14: "IDSL - ISDN Digital Subscriber Line",
        15: "Ethernet",
        16: "xDSL - Digital Subscriber Line of unknown type",
        17: "Cable",
        18: "Wireles - Other",
        19: "Wireless - IEEE 802.11",
        20: "Token-Ring",
        21: "FDDI",
        22: "Wireless - CDMA2000",
        23: "Wireless - UMTS",
        24: "Wireless - 1X-EV",
        25: "IAPP",
        26: "FTTP - Fiber to the Premises",
        27: "Wireless - IEEE 802.16",
        28: "Wireless - IEEE 802.20",
        29: "Wireless - IEEE 802.22",
        30: "PPPoA - PPP over ATM",
        31: "PPPoEoA - PPP over Ethernet over ATM",
        32: "PPPoEoE - PPP over Ethernet over Ethernet",
        33: "PPPoEoVLAN - PPP over Ethernet over VLAN",
        34: "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ",
        35: "xPON - Passive Optical Network",
        36: "Wireless - XGP",
        37: "WiMAX Pre-Release 8 IWK Function",
        38: "WIMAX-WIFI-IWK: WiMAX WIFI Interworking",
        39: "WIMAX-SFF: Signaling Forwarding Function for LTE/3GPP2",
        40: "WIMAX-HA-LMA: WiMAX HA and or LMA function",
        41: "WIMAX-DHCP: WIMAX DHCP service",
        42: "WIMAX-LBS: WiMAX location based service",
        43: "WIMAX-WVS: WiMAX voice service"
    },

    # Tunnel-Type
    64:
    {
        1: "Point-to-Point Tunneling Protocol (PPTP)",
        2: "Layer Two Forwarding (L2F)",
        3: "Layer Two Tunneling Protocol (L2TP)",
        4: "Ascend Tunnel Management Protocol (ATMP)",
        5: "Virtual Tunneling Protocol (VTP)",
        6: "IP Authentication Header in the Tunnel-mode (AH)",
        7: "IP-in-IP Encapsulation (IP-IP)",
        8: "Minimal IP-in-IP Encapsulation (MIN-IP-IP)",
        9: "IP Encapsulating Security Payload in the Tunnel-mode (ESP)",
        10: "Generic Route Encapsulation (GRE)",
        11: "Bay Dial Virtual Services (DVS)",
        12: "IP-in-IP Tunneling",
        13: "Virtual LANs (VLAN)"
    },

    # Tunnel-Medium-Type
    65:
    {
        1: "IPv4 (IP version 4)",
        2: "IPv6 (IP version 6)",
        3: "NSAP",
        4: "HDLC (8-bit multidrop)",
        5: "BBN 1822",
        6: "802",
        7: "E.163 (POTS)",
        8: "E.164 (SMDS, Frame Relay, ATM)",
        9: "F.69 (Telex)",
        10: "X.121 (X.25, Frame Relay)",
        11: "IPX",
        12: "Appletalk",
        13: "Decnet IV",
        14: "Banyan Vine",
        15: "E.164 with NSAP format subaddress"
    },

    # ARAP-Zone-Access
    72:
    {
        1: "Only allow access to default zone",
        2: "Use zone filter inclusively",
        3: "Not used",
        4: "Use zone filter exclusively"
    },

    # Prompt
    76:
    {
        0: "No Echo",
        1: "Echo"
    },

    # Error-Cause Attribute
    101:
    {
        201: "Residual Session Context Removed",
        202: "Invalid EAP Packet (Ignored)",
        401: "Unsupported Attribute",
        402: "Missing Attribute",
        403: "NAS Identification Mismatch",
        404: "Invalid Request",
        405: "Unsupported Service",
        406: "Unsupported Extension",
        407: "Invalid Attribute Value",
        501: "Administratively Prohibited",
        502: "Request Not Routable (Proxy)",
        503: "Session Context Not Found",
        504: "Session Context Not Removable",
        505: "Other Proxy Processing Error",
        506: "Resources Unavailable",
        507: "Request Initiated",
        508: "Multiple Session Selection Unsupported",
        509: "Location-Info-Required",
        601: "Response Too Big"
    },

    # Operator Namespace Identifier - Attribute 126
    126:
    {
        0x30: "TADIG",
        0x31: "REALM",
        0x32: "E212",
        0x33: "ICC",
        0xFF: "Reserved"
    },

    # Basic-Location-Policy-Rules
    129:
    {
        0: "Retransmission allowed",
    },

    # Location-Capable
    131:
    {
        1: "CIVIC_LOCATION",
        2: "GEO_LOCATION",
        4: "USERS_LOCATION",
        8: "NAS_LOCATION"
    },

    # Framed-Management-Protocol
    133:
    {
        1: "SNMP",
        2: "Web-based",
        3: "NETCONF",
        4: "FTP",
        5: "TFTP",
        6: "SFTP",
        7: "RCP",
        8: "SCP"
    },

    # Management-Transport-Protection
    134:
    {
        1: "No-Protection",
        2: "Integrity-Protection",
        3: "Integrity-Confidentiality-Protection",
    },
}


class _RadiusAttrIntEnumVal(_SpecificRadiusAttr):
    """
    Implements a RADIUS attribute which value field is 4 bytes long integer.
    """

    __slots__ = ["val"]

    fields_desc = [
        ByteEnumField("type", 6, _radius_attribute_types),
        ByteField("len", 6),
        MultiEnumField(
            "value",
            0,
            _radius_attrs_values,
            depends_on=lambda p: p.type,
            fmt="I"
        )
    ]


class RadiusAttr_Service_Type(_RadiusAttrIntEnumVal):
    """RFC 2865"""
    val = 6


class RadiusAttr_Framed_Protocol(_RadiusAttrIntEnumVal):
    """RFC 2865"""
    val = 7


class RadiusAttr_Acct_Status_Type(_RadiusAttrIntEnumVal):
    """RFC 2866"""
    val = 40


class RadiusAttr_Acct_Authentic(_RadiusAttrIntEnumVal):
    """RFC 2866"""
    val = 45


class RadiusAttr_Acct_Terminate_Cause(_RadiusAttrIntEnumVal):
    """RFC 2866"""
    val = 49


class RadiusAttr_NAS_Port_Type(_RadiusAttrIntEnumVal):
    """RFC 2865"""
    val = 61


class _EAPPacketField(PacketLenField):
    """
    Handles EAP-Message attribute value (the actual EAP packet).
    """

    def m2i(self, pkt, m):
        ret = None
        eap_packet_len = struct.unpack("!H", m[2:4])[0]
        if eap_packet_len < 254:
            # If the EAP packet has not been fragmented, build a Scapy EAP
            # packet from the data.
            ret = EAP(m)
        else:
            ret = conf.raw_layer(m)
        return ret


class RadiusAttr_EAP_Message(RadiusAttribute):
    """
    Implements the "EAP-Message" attribute (RFC 3579).
    """
    name = "EAP-Message"
    match_subclass = True
    fields_desc = [
        ByteEnumField("type", 79, _radius_attribute_types),
        FieldLenField(
            "len",
            None,
            "value",
            "B",
            adjust=lambda pkt, x: x + 2
        ),
        _EAPPacketField("value", "", EAP, length_from=lambda p: p.len - 2)
    ]

    def post_dissect(self, s):
        if not conf.contribs.get("radius", {}).get("auto-defrag", True):
            return s
        if isinstance(self.value, conf.raw_layer):
            # Defragment
            x = s
            buf = self.value.load
            while x and struct.unpack("!B", x[:1])[0] == 79:
                # Let's carefully avoid the infinite loop
                length = struct.unpack("!B", x[1:2])[0]
                if not length:
                    return s
                buf, x = buf + x[2:length], x[length:]
                if length < 254:
                    self.value = EAP(buf)
                    return x
        return s


class RadiusAttr_Vendor_Specific(RadiusAttribute):
    """
    Implements the "Vendor-Specific" attribute, as described in RFC 2865.
    """

    name = "Vendor-Specific"
    match_subclass = True
    fields_desc = [
        ByteEnumField("type", 26, _radius_attribute_types),
        FieldLenField(
            "len",
            None,
            "value",
            "B",
            adjust=lambda pkt, x: len(pkt.value) + 8
        ),
        IntField("vendor_id", 0),
        ByteField("vendor_type", 0),
        FieldLenField(
            "vendor_len",
            None,
            "value",
            "B",
            adjust=lambda p, x: len(p.value) + 2
        ),
        StrLenField("value", "", length_from=lambda p: p.vendor_len - 2)
    ]


# See IANA RADIUS Packet Type Codes registry
_packet_codes = {
    1: "Access-Request",
    2: "Access-Accept",
    3: "Access-Reject",
    4: "Accounting-Request",
    5: "Accounting-Response",
    6: "Accounting-Status (now Interim Accounting)",
    7: "Password-Request",
    8: "Password-Ack",
    9: "Password-Reject",
    10: "Accounting-Message",
    11: "Access-Challenge",
    12: "Status-Server (experimental)",
    13: "Status-Client (experimental)",
    21: "Resource-Free-Request",
    22: "Resource-Free-Response",
    23: "Resource-Query-Request",
    24: "Resource-Query-Response",
    25: "Alternate-Resource-Reclaim-Request",
    26: "NAS-Reboot-Request",
    27: "NAS-Reboot-Response",
    28: "Reserved",
    29: "Next-Passcode",
    30: "New-Pin",
    31: "Terminate-Session",
    32: "Password-Expired",
    33: "Event-Request",
    34: "Event-Response",
    40: "Disconnect-Request",
    41: "Disconnect-ACK",
    42: "Disconnect-NAK",
    43: "CoA-Request",
    44: "CoA-ACK",
    45: "CoA-NAK",
    50: "IP-Address-Allocate",
    51: "IP-Address-Release",
    52: "Protocol-Error",
    250: "Experimental Use",
    251: "Experimental Use",
    252: "Experimental Use",
    253: "Experimental Use",
    254: "Reserved",
    255: "Reserved"
}


class Radius(Packet):
    """
    Implements a RADIUS packet (RFC 2865).
    """

    name = "RADIUS"
    fields_desc = [
        ByteEnumField("code", 1, _packet_codes),
        ByteField("id", 0),
        FieldLenField(
            "len",
            None,
            "attributes",
            "H",
            adjust=lambda pkt, x: len(pkt.attributes) + 20
        ),
        XStrFixedLenField("authenticator", "", 16),
        PacketListField(
            "attributes",
            [],
            RadiusAttribute,
            length_from=lambda pkt: pkt.len - 20
        )
    ]

    def compute_authenticator(self, packed_request_auth, shared_secret):
        """
        Computes the authenticator field (RFC 2865 - Section 3)
        """

        data = prepare_packed_data(self, packed_request_auth)
        radius_mac = hashlib.md5(data + shared_secret)
        return radius_mac.digest()

    def post_build(self, p, pay):
        p += pay
        length = self.len
        if length is None:
            length = len(p)
            p = p[:2] + struct.pack("!H", length) + p[4:]
        return p


bind_bottom_up(UDP, Radius, sport=1812)
bind_bottom_up(UDP, Radius, dport=1812)
bind_bottom_up(UDP, Radius, sport=1813)
bind_bottom_up(UDP, Radius, dport=1813)
bind_bottom_up(UDP, Radius, sport=3799)
bind_bottom_up(UDP, Radius, dport=3799)
bind_layers(UDP, Radius, sport=1812, dport=1812)
