diff --git a/agents/librad.go b/agents/librad.go index ec5327ee8..dfffd5b09 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -19,23 +19,37 @@ along with this program. If not, see package agents import ( + "strings" + "github.com/cgrates/cgrates/utils" "github.com/cgrates/radigo" ) -/* -Various RADIUS helpers here -*/ - func radPassesFieldFilter(pkt *radigo.Packet, fieldFilter *utils.RSRField, processorVars map[string]string) (pass bool) { if fieldFilter == nil { return true } if val, hasIt := processorVars[fieldFilter.Id]; hasIt { // ProcessorVars have priority if fieldFilter.FilterPasses(val) { - return true + pass = true } - return false + return } - return + splt := strings.Split(fieldFilter.Id, "/") + var attrName, vendorName string + if len(splt) > 1 { + vendorName, attrName = splt[0], splt[1] + } else { + attrName = splt[0] + } + avps := pkt.AttributesWithName(attrName, vendorName) + if len(avps) == 0 { // no attribute found, filter not passing + return + } + for _, avp := range avps { // they all need to match the filter + if !fieldFilter.FilterPasses(avp.StringValue()) { + return + } + } + return true } diff --git a/agents/librad_test.go b/agents/librad_test.go index 2f5d97c32..a26dcf563 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -17,3 +17,90 @@ along with this program. If not, see */ package agents + +import ( + "fmt" + "strings" + "testing" + + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/radigo" +) + +var ( + dictRad *radigo.Dictionary + coder radigo.Coder +) + +var freeRADIUSDocDictSample = ` +# Most of the lines are copied from freeradius documentation here: +# http://networkradius.com/doc/3.0.10/concepts/dictionary/introduction.html + +# Attributes +ATTRIBUTE User-Name 1 string +ATTRIBUTE Password 2 string + +# Alias values +VALUE Framed-Protocol PPP 1 + +# Vendors +VENDOR Cisco 9 +VENDOR Microsoft 311 + +# Vendor AVPs +BEGIN-VENDOR Cisco +ATTRIBUTE Cisco-AVPair 1 string +ATTRIBUTE Cisco-NAS-Port 2 string +END-VENDOR Cisco +` + +func init() { + dictRad = radigo.RFC2865Dictionary() + // Load some VSA for our tests + dictRad.ParseFromReader(strings.NewReader(freeRADIUSDocDictSample)) + coder = radigo.NewCoder() +} + +func TestRadPassesFieldFilter(t *testing.T) { + pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org") + if err := pkt.AddAVPWithName("User-Name", "flopsy", ""); err != nil { + t.Error(err) + } + if err := pkt.AddAVPWithName("Cisco-NAS-Port", "CGR1", "Cisco"); err != nil { + t.Error(err) + } + //ftr := + if !radPassesFieldFilter(pkt, nil, nil) { + t.Error("not passing empty filter") + } + if !radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile("User-Name(flopsy)"), nil) { + t.Error("not passing valid filter") + } + if radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile("User-Name(notmatching)"), nil) { + t.Error("passing invalid filter value") + } + if !radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile("Cisco/Cisco-NAS-Port(CGR1)"), nil) { + t.Error("not passing valid filter") + } + if radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile("Cisco/Cisco-NAS-Port(notmatching)"), nil) { + t.Error("passing invalid filter value") + } + if !radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(4)", MetaRadReqCode)), + map[string]string{MetaRadReqCode: "4"}) { + t.Error("not passing valid filter") + } + if radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(4)", MetaRadReqCode)), + map[string]string{MetaRadReqCode: "5"}) { + t.Error("passing invalid filter") + } + if radPassesFieldFilter(pkt, + utils.NewRSRFieldMustCompile("UnknownField(notmatching)"), nil) { + t.Error("passing invalid filter value") + } +} diff --git a/agents/radagent.go b/agents/radagent.go index 59e3cb628..0798b551b 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -26,6 +26,11 @@ import ( "github.com/cgrates/rpcclient" ) +const ( + MetaRadReqCode = "*req_code" + MetaRadReplyCode = "*reply_code" +) + func NewRadiusAgent(cgrCfg *config.CGRConfig, smg rpcclient.RpcClientConnection) (ra *RadiusAgent, err error) { dicts := make(map[string]*radigo.Dictionary, len(cgrCfg.RadiusAgentCfg().ClientDictionaries)) for clntID, dictPath := range cgrCfg.RadiusAgentCfg().ClientDictionaries { @@ -49,29 +54,71 @@ type RadiusAgent struct { rsAcct *radigo.Server } +// handleAuth handles RADIUS Authorization request func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err error) { + req.SetAVPValues() // populate string values in AVPs utils.Logger.Debug(fmt.Sprintf("RadiusAgent handleAuth, received request: %+v", req)) + procVars := map[string]string{ + MetaRadReqCode: "4", + } rpl = req.Reply() rpl.Code = radigo.AccessAccept - for _, avp := range req.AVPs { - rpl.AVPs = append(rpl.AVPs, avp) + var processed bool + for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors { + var lclProcessed bool + if lclProcessed, err = ra.processRequest(reqProcessor, req, procVars, rpl); lclProcessed { + processed = lclProcessed + } + if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) { + break + } + } + if err != nil { + utils.Logger.Err(fmt.Sprintf(" request: %s, error: %s", utils.ToJSON(req), err.Error())) + return nil, nil + } else if !processed { + utils.Logger.Err(fmt.Sprintf(" No request processor enabled for request: %s, ignoring request", utils.ToJSON(req))) + return nil, nil } return } +// handleAcct handles RADIUS Accounting request +// supports: Acct-Status-Type = Start, Interim-Update, Stop func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err error) { - req.SetAVPValues() + req.SetAVPValues() // populate string values in AVPs utils.Logger.Debug(fmt.Sprintf("Received request: %s", utils.ToJSON(req))) + procVars := map[string]string{ + MetaRadReqCode: "4", + } rpl = req.Reply() rpl.Code = radigo.AccountingResponse - rpl.SetAVPValues() + var processed bool + for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors { + var lclProcessed bool + if lclProcessed, err = ra.processRequest(reqProcessor, req, procVars, rpl); lclProcessed { + processed = lclProcessed + } + if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) { + break + } + } + if err != nil { + utils.Logger.Err(fmt.Sprintf(" request: %s, error: %s", utils.ToJSON(req), err.Error())) + return nil, nil + } else if !processed { + utils.Logger.Err(fmt.Sprintf(" No request processor enabled for request: %s, ignoring request", utils.ToJSON(req))) + return nil, nil + } return } -func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.RARequestProcessor) (processed bool, err error) { +// processRequest represents one processor processing the request +func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor, + req *radigo.Packet, processorVars map[string]string, reply *radigo.Packet) (processed bool, err error) { passesAllFilters := true for _, fldFilter := range reqProcessor.RequestFilter { - if passes := radPassesFieldFilter(req, fldFilter, nil); !passes { + if passes := radPassesFieldFilter(req, fldFilter, processorVars); !passes { passesAllFilters = false } } diff --git a/data/conf/samples/radagent/cgrates.json b/data/conf/samples/radagent/cgrates.json index cb7f38a01..e950c93c4 100644 --- a/data/conf/samples/radagent/cgrates.json +++ b/data/conf/samples/radagent/cgrates.json @@ -81,8 +81,8 @@ { "id": "KamailioAccountingStart", "dry_run": false, - "request_filter": "Service-Type(Sip-Session);Sip-Method(Invite)", - "flags": [], + "request_filter": "*radcode(4);Service-Type(Sip-Session);Sip-Method(Invite)", + "flags": ["*radacct_start"], // flag accounting start since we have no Acct-Status-Type in request "continue_on_success": false, "append_reply": true, "request_fields":[ @@ -94,8 +94,8 @@ { "id": "KamailioAccountingStop", "dry_run": false, - "request_filter": "Service-Type(Sip-Session);Sip-Method(Bye)", - "flags": [], + "request_filter": "*radcode(4)Service-Type(Sip-Session);Sip-Method(Bye)", + "flags": ["*radacct_stop"], "continue_on_success": false, "append_reply": true, "request_fields":[ diff --git a/glide.lock b/glide.lock index ea042a637..142d23c60 100644 --- a/glide.lock +++ b/glide.lock @@ -18,7 +18,7 @@ imports: - name: github.com/cgrates/osipsdagram version: 3d6beed663452471dec3ca194137a30d379d9e8f - name: github.com/cgrates/radigo - version: 89761b5c2d44a89393c57c52b6ee472e77138bfb + version: b6d2c57d9667095035ad4ffd0395bbbebb63ee94 - name: github.com/cgrates/rpcclient version: dddae42e9344e877627cd4b7aba075d63b452c0b - name: github.com/ChrisTrenkamp/goxpath diff --git a/utils/rsrfield.go b/utils/rsrfield.go index 6c387081c..097031727 100644 --- a/utils/rsrfield.go +++ b/utils/rsrfield.go @@ -88,6 +88,14 @@ func NewRSRField(fldStr string) (*RSRField, error) { return rsrField, nil } +func NewRSRFieldMustCompile(fldStr string) (rsrFld *RSRField) { + var err error + if rsrFld, err = NewRSRField(fldStr); err != nil { + return nil + } + return +} + type RSRField struct { Id string // Identifier Rules string // Rules container holding the string rules to be able to restore it after DB