mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 10:06:24 +05:00
CHAP authentication was always using the Request Authenticator as challenge, ignoring CHAP-Challenge AVP when present. Per RFC 2865, the CHAP-Challenge attribute takes precedence if included in the packet. Ref: #4963
171 lines
5.7 KiB
Go
171 lines
5.7 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package agents
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/cgrates/cgrates/utils"
|
|
"github.com/cgrates/radigo"
|
|
)
|
|
|
|
// radAppendAttributes appends attributes to a RADIUS packet based on predefined template
|
|
func radAppendAttributes(packet *radigo.Packet, nm *utils.OrderedNavigableMap) error {
|
|
for el := nm.GetFirstElement(); el != nil; el = el.Next() {
|
|
path := el.Value
|
|
cfgItm, _ := nm.Field(path)
|
|
path = path[:len(path)-1] // remove the last index
|
|
if path[0] == MetaRadReplyCode { // Special case used to control the reply code of RADIUS reply
|
|
if err := packet.SetCodeWithName(utils.IfaceAsString(cfgItm.Data)); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
var attrName, vendorName string
|
|
if len(path) > 1 {
|
|
vendorName, attrName = path[0], path[1]
|
|
} else {
|
|
attrName = path[0]
|
|
}
|
|
|
|
if err := packet.AddAVPWithName(attrName, utils.IfaceAsString(cfgItm.Data), vendorName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// newRADataProvider constructs a DataProvider
|
|
func newRADataProvider(req *radigo.Packet) (dP utils.DataProvider) {
|
|
dP = &radiusDP{req: req, cache: utils.MapStorage{}}
|
|
return
|
|
}
|
|
|
|
// radiusDP implements utils.DataProvider, serving as radigo.Packet data decoder
|
|
// decoded data is only searched once and cached
|
|
type radiusDP struct {
|
|
req *radigo.Packet
|
|
cache utils.MapStorage
|
|
}
|
|
|
|
// String is part of utils.DataProvider interface
|
|
// when called, it will display the already parsed values out of cache
|
|
func (pk *radiusDP) String() string {
|
|
return utils.ToIJSON(pk.req) // return ToJSON because Packet don't have a string method
|
|
}
|
|
|
|
// FieldAsInterface is part of utils.DataProvider interface
|
|
func (pk *radiusDP) FieldAsInterface(fldPath []string) (data any, err error) {
|
|
if len(fldPath) != 1 {
|
|
return nil, utils.ErrNotFound
|
|
}
|
|
if data, err = pk.cache.FieldAsInterface(fldPath); err != nil {
|
|
if err != utils.ErrNotFound { // item found in cache
|
|
return
|
|
}
|
|
err = nil // cancel previous err
|
|
} else {
|
|
return // data found in cache
|
|
}
|
|
if len(pk.req.AttributesWithName(fldPath[0], "")) != 0 {
|
|
data = pk.req.AttributesWithName(fldPath[0], "")[0].GetStringValue()
|
|
}
|
|
pk.cache.Set(fldPath, data)
|
|
return
|
|
}
|
|
|
|
// FieldAsString is part of utils.DataProvider interface
|
|
func (pk *radiusDP) FieldAsString(fldPath []string) (data string, err error) {
|
|
var valIface any
|
|
valIface, err = pk.FieldAsInterface(fldPath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return utils.IfaceAsString(valIface), nil
|
|
}
|
|
|
|
// radauthReq is used to authorize a request based on flags
|
|
func radauthReq(flags utils.FlagsWithParams, req *radigo.Packet, aReq *AgentRequest, rpl *radigo.Packet) (bool, error) {
|
|
// try to get UserPassword from Vars as slice of NMItems
|
|
nmItems, has := aReq.Vars.Map[utils.UserPassword]
|
|
if !has {
|
|
return false, utils.ErrNotFound
|
|
}
|
|
pass := nmItems.Slice[0].Value.String()
|
|
switch {
|
|
case flags.Has(utils.MetaPAP):
|
|
userPassAvps := req.AttributesWithName(UserPasswordAVP, utils.EmptyString)
|
|
if len(userPassAvps) == 0 {
|
|
return false, utils.NewErrMandatoryIeMissing(UserPasswordAVP)
|
|
}
|
|
return userPassAvps[0].StringValue == pass, nil
|
|
case flags.Has(utils.MetaCHAP):
|
|
chapAVPs := req.AttributesWithName(CHAPPasswordAVP, utils.EmptyString)
|
|
if len(chapAVPs) == 0 {
|
|
return false, utils.NewErrMandatoryIeMissing(CHAPPasswordAVP)
|
|
}
|
|
// RFC 2865: Use CHAP-Challenge AVP if present, otherwise Request Authenticator.
|
|
challenge := req.Authenticator[:]
|
|
if chapChallengeAVPs := req.AttributesWithName(CHAPChallengeAVP, utils.EmptyString); len(chapChallengeAVPs) > 0 {
|
|
challenge = chapChallengeAVPs[0].RawValue
|
|
}
|
|
return radigo.AuthenticateCHAP([]byte(pass), challenge, chapAVPs[0].RawValue), nil
|
|
case flags.Has(utils.MetaMSCHAPV2):
|
|
msChallenge := req.AttributesWithName(MSCHAPChallengeAVP, MicrosoftVendor)
|
|
if len(msChallenge) == 0 {
|
|
return false, utils.NewErrMandatoryIeMissing(MSCHAPChallengeAVP)
|
|
}
|
|
msResponse := req.AttributesWithName(MSCHAP2ResponseAVP, MicrosoftVendor)
|
|
if len(msResponse) == 0 {
|
|
return false, utils.NewErrMandatoryIeMissing(MSCHAP2ResponseAVP)
|
|
}
|
|
vsaMSResponde := msResponse[0].Value.(*radigo.VSA).RawValue
|
|
vsaMSChallange := msChallenge[0].Value.(*radigo.VSA).RawValue
|
|
|
|
userName := req.AttributesWithName("User-Name", utils.EmptyString)[0].StringValue
|
|
|
|
if len(vsaMSChallange) != 16 || len(vsaMSResponde) != 50 {
|
|
return false, nil
|
|
}
|
|
ident := vsaMSResponde[0]
|
|
peerChallenge := vsaMSResponde[2:18]
|
|
peerResponse := vsaMSResponde[26:50]
|
|
ntResponse, err := radigo.GenerateNTResponse(vsaMSChallange,
|
|
peerChallenge, userName, pass)
|
|
if err != nil || !bytes.Equal(ntResponse, peerResponse) {
|
|
return false, err
|
|
}
|
|
|
|
authenticatorResponse, err := radigo.GenerateAuthenticatorResponse(vsaMSChallange, peerChallenge,
|
|
ntResponse, userName, pass)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
success := make([]byte, 43)
|
|
success[0] = ident
|
|
copy(success[1:], authenticatorResponse)
|
|
// this AVP need to be added to be verified on the client side
|
|
rpl.AddAVPWithName(MSCHAP2SuccessAVP, string(success), MicrosoftVendor)
|
|
return true, nil
|
|
default:
|
|
return false, utils.NewErrMandatoryIeMissing(utils.Flags)
|
|
}
|
|
|
|
}
|