184 lines
4.8 KiB
Go
184 lines
4.8 KiB
Go
package radius
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
// MaxPacketLength is the maximum wire length of a RADIUS packet.
|
|
const MaxPacketLength = 4096
|
|
|
|
// Packet is a RADIUS packet.
|
|
type Packet struct {
|
|
Code Code
|
|
Identifier byte
|
|
Authenticator [16]byte
|
|
Secret []byte
|
|
Attributes
|
|
}
|
|
|
|
// New creates a new packet with the Code, Secret fields set to the given
|
|
// values. The returned packet's Identifier and Authenticator fields are filled
|
|
// with random values.
|
|
//
|
|
// The function panics if not enough random data could be generated.
|
|
func New(code Code, secret []byte) *Packet {
|
|
var buff [17]byte
|
|
if _, err := rand.Read(buff[:]); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
packet := &Packet{
|
|
Code: code,
|
|
Identifier: buff[0],
|
|
Secret: secret,
|
|
}
|
|
copy(packet.Authenticator[:], buff[1:])
|
|
return packet
|
|
}
|
|
|
|
// Parse parses an encoded RADIUS packet b. An error is returned if the packet
|
|
// is malformed.
|
|
func Parse(b, secret []byte) (*Packet, error) {
|
|
if len(b) < 20 {
|
|
return nil, errors.New("radius: packet not at least 20 bytes long")
|
|
}
|
|
|
|
length := int(binary.BigEndian.Uint16(b[2:4]))
|
|
if length < 20 || length > MaxPacketLength || len(b) < length {
|
|
return nil, errors.New("radius: invalid packet length")
|
|
}
|
|
|
|
attrs, err := ParseAttributes(b[20:length])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
packet := &Packet{
|
|
Code: Code(b[0]),
|
|
Identifier: b[1],
|
|
Secret: secret,
|
|
Attributes: attrs,
|
|
}
|
|
copy(packet.Authenticator[:], b[4:20])
|
|
return packet, nil
|
|
}
|
|
|
|
// Response returns a new packet that has the same identifier, secret, and
|
|
// authenticator as the current packet.
|
|
func (p *Packet) Response(code Code) *Packet {
|
|
q := &Packet{
|
|
Code: code,
|
|
Identifier: p.Identifier,
|
|
Secret: p.Secret,
|
|
}
|
|
copy(q.Authenticator[:], p.Authenticator[:])
|
|
return q
|
|
}
|
|
|
|
// Encode encodes the RADIUS packet to wire format that can then
|
|
// be sent to a RADIUS client.
|
|
//
|
|
// If the RADIUS packet code requires it, the authenticator in
|
|
// the returned data will be a hash calculation based off of the packet
|
|
// data and secret. Use MarshalBinary() to get the packet in wire
|
|
// format without the hash calculation.
|
|
//
|
|
// An error is returned if the encoded packet is too long (due to its Attributes),
|
|
// or if the packet has an unknown Code.
|
|
func (p *Packet) Encode() ([]byte, error) {
|
|
b, err := p.MarshalBinary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch p.Code {
|
|
case CodeAccessRequest, CodeStatusServer:
|
|
// Authenticator is sent as-is
|
|
case CodeAccessAccept, CodeAccessReject, CodeAccountingRequest, CodeAccountingResponse, CodeAccessChallenge, CodeDisconnectRequest, CodeDisconnectACK, CodeDisconnectNAK, CodeCoARequest, CodeCoAACK, CodeCoANAK:
|
|
hash := md5.New()
|
|
hash.Write(b[:4])
|
|
switch p.Code {
|
|
case CodeAccountingRequest, CodeDisconnectRequest, CodeCoARequest:
|
|
var nul [16]byte
|
|
hash.Write(nul[:])
|
|
default:
|
|
hash.Write(p.Authenticator[:])
|
|
}
|
|
hash.Write(b[20:])
|
|
hash.Write(p.Secret)
|
|
hash.Sum(b[4:4:20])
|
|
default:
|
|
return nil, errors.New("radius: unknown Packet Code")
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// MarshalBinary returns the packet in wire format.
|
|
//
|
|
// The authenticator in the returned data is copied from p.Authenticator
|
|
// without any hash calculation. Use Encode() if the packet is intended
|
|
// to be sent to a RADIUS client and requires the authenticator to be
|
|
// calculated.
|
|
func (p *Packet) MarshalBinary() ([]byte, error) {
|
|
attributesLen, err := AttributesEncodedLen(p.Attributes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size := 20 + attributesLen
|
|
if size > MaxPacketLength {
|
|
return nil, errors.New("radius: packet is too large")
|
|
}
|
|
b := make([]byte, size)
|
|
b[0] = byte(p.Code)
|
|
b[1] = p.Identifier
|
|
binary.BigEndian.PutUint16(b[2:4], uint16(size))
|
|
copy(b[4:20], p.Authenticator[:])
|
|
p.Attributes.encodeTo(b[20:])
|
|
return b, nil
|
|
}
|
|
|
|
// IsAuthenticResponse returns if the given RADIUS response is an authentic
|
|
// response to the given request.
|
|
func IsAuthenticResponse(response, request, secret []byte) bool {
|
|
if len(response) < 20 || len(request) < 20 || len(secret) == 0 {
|
|
return false
|
|
}
|
|
|
|
hash := md5.New()
|
|
hash.Write(response[:4])
|
|
hash.Write(request[4:20])
|
|
hash.Write(response[20:])
|
|
hash.Write(secret)
|
|
var sum [md5.Size]byte
|
|
return bytes.Equal(hash.Sum(sum[:0]), response[4:20])
|
|
}
|
|
|
|
// IsAuthenticRequest returns if the given RADIUS request is an authentic
|
|
// request using the given secret.
|
|
func IsAuthenticRequest(request, secret []byte) bool {
|
|
if len(request) < 20 || len(secret) == 0 {
|
|
return false
|
|
}
|
|
|
|
switch Code(request[0]) {
|
|
case CodeAccessRequest, CodeStatusServer:
|
|
return true
|
|
case CodeAccountingRequest, CodeDisconnectRequest, CodeCoARequest:
|
|
hash := md5.New()
|
|
hash.Write(request[:4])
|
|
var nul [16]byte
|
|
hash.Write(nul[:])
|
|
hash.Write(request[20:])
|
|
hash.Write(secret)
|
|
var sum [md5.Size]byte
|
|
return bytes.Equal(hash.Sum(sum[:0]), request[4:20])
|
|
default:
|
|
return false
|
|
}
|
|
}
|