mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Updated radigo library to latest version. Updated RadiusAgent to satisfy the birpc client interface. Added *radDAdiscMsg OrderedNavigableMap type field within AgentRequest. This one is similar to *diamreq, as it is used for building RADIUS server-initiated Disconnect Requests. radReplyAppendAttributes: refactored to reflect that it can now be used to also append attributes to request packets, not only reply. Added bidirectional support for session related RadiusAgent methods. For Dynamic Authorization to be possible, a new field was added within RadiusAgent that holds dicts and secrets only for the clients that support it. They are used to create the DA Client sending Disconnect Requests. Added a new cache partition to store Access-Request packets with the purpose of using them to build the Disconnect Requests. They are identified by sessionID. It defaults to the value of 'Acct-Session-id'. Added a predefined '*dmr' template as well as a 'dmr_template' config option within the 'radius_agent' config section. This will map to a custom or to the predefined template and will be used to build the Disconnect Request. By default, it doesn't point to any template (this also means that the Access-Request packets will not be cached). Another option added to 'radius_agent' is 'client_da_addresses', which lists the RADIUS clients supporting Dynamic Authorization. The key represents the host of the client, while the value represents the address to which we will send the Disconnect Request. Added integration test.
167 lines
5.5 KiB
Go
167 lines
5.5 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 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://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)
|
|
}
|
|
return radigo.AuthenticateCHAP([]byte(pass),
|
|
req.Authenticator[:], 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(MSCHAPResponseAVP, MicrosoftVendor)
|
|
if len(msResponse) == 0 {
|
|
return false, utils.NewErrMandatoryIeMissing(MSCHAPResponseAVP)
|
|
}
|
|
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)
|
|
}
|
|
|
|
}
|