diff --git a/agents/librad.go b/agents/librad.go index ac672efca..e08cfe455 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -21,102 +21,39 @@ package agents import ( "fmt" "net" - "strings" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "github.com/cgrates/radigo" ) -// radAttrVendorFromPath returns AttributenName and VendorName from path -// path should be the form attributeName or vendorName/attributeName -func attrVendorFromPath(path string) (attrName, vendorName string) { - splt := strings.Split(path, utils.NestingSep) - if len(splt) > 2 { - vendorName, attrName = splt[1], splt[2] - } else { - attrName = splt[1] - } - return -} - -// radComposedFieldValue extracts the field value out of RADIUS packet -// procVars have priority over packet variables -func radComposedFieldValue(pkt *radigo.Packet, - agReq *AgentRequest, outTpl config.RSRParsers) (outVal string) { - for _, rsrTpl := range outTpl { - if out, err := rsrTpl.ParseDataProvider(agReq, utils.NestingSep); err != nil { - utils.Logger.Warning( - fmt.Sprintf("<%s> %s", - utils.RadiusAgent, err.Error())) - continue - } else { - outVal += out - continue - } - for _, avp := range pkt.AttributesWithName( - attrVendorFromPath(rsrTpl.Rules)) { - if parsed, err := rsrTpl.ParseValue(avp.GetStringValue()); err != nil { - utils.Logger.Warning( - fmt.Sprintf("<%s> %s", - utils.RadiusAgent, err.Error())) - } else { - outVal += parsed - } - } - } - return outVal -} - -// radFieldOutVal formats the field value retrieved from RADIUS packet -func radFieldOutVal(pkt *radigo.Packet, agReq *AgentRequest, - cfgFld *config.FCTemplate) (outVal string, err error) { - // different output based on cgrFld.Type - switch cfgFld.Type { - case utils.META_FILLER: - outVal, err = cfgFld.Value.ParseValue(utils.EmptyString) - cfgFld.Padding = utils.MetaRight - case utils.META_CONSTANT: - outVal, err = cfgFld.Value.ParseValue(utils.EmptyString) - case utils.META_COMPOSED, utils.MetaVariable: - outVal = radComposedFieldValue(pkt, agReq, cfgFld.Value) - case utils.META_NONE: - return - default: - return utils.EmptyString, fmt.Errorf("unsupported configuration field type: <%s>", cfgFld.Type) - } - if err != nil { - return - } - if outVal, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { - return utils.EmptyString, err - } - return -} - // radReplyAppendAttributes appends attributes to a RADIUS reply based on predefined template -func radReplyAppendAttributes(reply *radigo.Packet, agReq *AgentRequest, - cfgFlds []*config.FCTemplate) (err error) { - for _, cfgFld := range cfgFlds { - fmtOut, err := radFieldOutVal(reply, agReq, cfgFld) - if err != nil { - return err +func radReplyAppendAttributes(reply *radigo.Packet, rplNM *config.NavigableMap) (err error) { + for _, val := range rplNM.Values() { + nmItms, isNMItems := val.([]*config.NMItem) + if !isNMItems { + return fmt.Errorf("cannot encode reply value: %s, err: not NMItems", utils.ToJSON(val)) } - utils.Logger.Debug(utils.ToJSON(cfgFld)) - if cfgFld.Path != utils.EmptyString || cfgFld.Type == utils.MetaRemoveAll { - if cfgFld.Path == MetaRadReplyCode { // Special case used to control the reply code of RADIUS reply - if err = reply.SetCodeWithName(fmtOut); err != nil { - return err - } - continue - } - attrName, vendorName := attrVendorFromPath(cfgFld.Path) - if err = reply.AddAVPWithName(attrName, fmtOut, vendorName); err != nil { + // find out the first itm which is not an attribute + var itm *config.NMItem + if len(nmItms) == 1 { + itm = nmItms[0] + } + if itm.Path[0] == MetaRadReplyCode { // Special case used to control the reply code of RADIUS reply + if err = reply.SetCodeWithName(utils.IfaceAsString(itm.Data)); err != nil { return err } + continue } - if cfgFld.BreakOnSuccess { - break + var attrName, vendorName string + if len(itm.Path) > 2 { + vendorName, attrName = itm.Path[0], itm.Path[1] + } else { + attrName = itm.Path[0] + } + + if err = reply.AddAVPWithName(attrName, utils.IfaceAsString(itm.Data), vendorName); err != nil { + return err } } return diff --git a/agents/librad_test.go b/agents/librad_test.go index 5083d110d..41bd3b8f1 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -19,7 +19,6 @@ along with this program. If not, see package agents import ( - "fmt" "reflect" "strings" "testing" @@ -77,71 +76,25 @@ func init() { coder = radigo.NewCoder() } -func TestAttrVendorFromPath(t *testing.T) { - if attrName, vendorName := attrVendorFromPath("*rep.User-Name"); attrName != "User-Name" || - vendorName != "" { - t.Error("failed") - } - if attrName, vendorName := attrVendorFromPath("*rep.Cisco.Cisco-NAS-Port"); attrName != "Cisco-NAS-Port" || - vendorName != "Cisco" { - t.Error("failed") - } -} - -func TestRadComposedFieldValue(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) - } - agReq := NewAgentRequest(nil, nil, nil, nil, nil, "cgrates.org", "", nil, nil, nil) - agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false, false) - agReq.Vars.Set([]string{"Cisco"}, "CGR1", false, false) - agReq.Vars.Set([]string{"User-Name"}, "flopsy", false, false) - eOut := "*radAcctStart|flopsy|CGR1" - if out := radComposedFieldValue(pkt, agReq, - config.NewRSRParsersMustCompile("~*vars.*radReqType;|;~*vars.User-Name;|;~*vars.Cisco", true, utils.INFIELD_SEP)); out != eOut { - t.Errorf("Expecting: <%s>, received: <%s>", eOut, out) - } -} - -func TestRadFieldOutVal(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) - } - eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart) - agReq := NewAgentRequest(nil, nil, nil, nil, nil, "cgrates.org", "", nil, nil, nil) - agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false, false) - agReq.Vars.Set([]string{"Cisco"}, "CGR1", false, false) - agReq.Vars.Set([]string{"User-Name"}, "flopsy", false, false) - cfgFld := &config.FCTemplate{Tag: "ComposedTest", Type: utils.META_COMPOSED, Path: utils.Destination, - Value: config.NewRSRParsersMustCompile("~*vars.*radReqType;|;~*vars.User-Name;|;~*vars.Cisco", true, utils.INFIELD_SEP), Mandatory: true} - if outVal, err := radFieldOutVal(pkt, agReq, cfgFld); err != nil { - t.Error(err) - } else if outVal != eOut { - t.Errorf("Expecting: <%s>, received: <%s>", eOut, outVal) - } -} - func TestRadReplyAppendAttributes(t *testing.T) { rply := radigo.NewPacket(radigo.AccessRequest, 2, dictRad, coder, "CGRateS.org").Reply() rplyFlds := []*config.FCTemplate{ - &config.FCTemplate{Tag: "ReplyCode", Path: MetaRadReplyCode, Type: utils.META_COMPOSED, + &config.FCTemplate{Tag: "ReplyCode", Path: utils.MetaRep + utils.NestingSep + MetaRadReplyCode, + Type: utils.MetaVariable, Value: config.NewRSRParsersMustCompile("~*cgrep.Attributes.RadReply", true, utils.INFIELD_SEP)}, - &config.FCTemplate{Tag: "Acct-Session-Time", Path: "*rep.Acct-Session-Time", Type: utils.META_COMPOSED, + &config.FCTemplate{Tag: "Acct-Session-Time", Path: utils.MetaRep + utils.NestingSep + "Acct-Session-Time", + Type: utils.MetaVariable, Value: config.NewRSRParsersMustCompile("~*cgrep.MaxUsage{*duration_seconds}", true, utils.INFIELD_SEP)}, } agReq := NewAgentRequest(nil, nil, nil, nil, nil, "cgrates.org", "", nil, nil, nil) agReq.CGRReply.Set([]string{utils.CapMaxUsage}, time.Duration(time.Hour), false, false) agReq.CGRReply.Set([]string{utils.CapAttributes, "RadReply"}, "AccessAccept", false, false) agReq.CGRReply.Set([]string{utils.CapAttributes, utils.Account}, "1001", false, false) - if err := radReplyAppendAttributes(rply, agReq, rplyFlds); err != nil { + + if err := agReq.SetFields(rplyFlds); err != nil { + t.Error(err) + } + if err := radReplyAppendAttributes(rply, agReq.Reply); err != nil { t.Error(err) } if rply.Code != radigo.AccessAccept { diff --git a/agents/radagent.go b/agents/radagent.go index 666835f7e..39705ad4e 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -103,6 +103,11 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e utils.RadiusAgent, utils.ToJSON(req))) return nil, nil } + if err := radReplyAppendAttributes(rpl, rplyNM); err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> err: %s, replying to message: %+v", + utils.RadiusAgent, err.Error(), utils.ToIJSON(req))) + return nil, err + } return } @@ -134,13 +139,18 @@ func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err e } if err != nil { utils.Logger.Err(fmt.Sprintf("<%s> error: <%s> ignoring request: %s, ", - utils.RadiusAgent, err.Error(), utils.ToJSON(req))) + utils.RadiusAgent, err.Error(), utils.ToIJSON(req))) return nil, nil } else if !processed { utils.Logger.Err(fmt.Sprintf("<%s> no request processor enabled, ignoring request %s", - utils.RadiusAgent, utils.ToJSON(req))) + utils.RadiusAgent, utils.ToIJSON(req))) return nil, nil } + if err := radReplyAppendAttributes(rpl, rplyNM); err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> err: %s, replying to message: %+v", + utils.RadiusAgent, err.Error(), utils.ToIJSON(req))) + return nil, err + } return } @@ -293,17 +303,16 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RequestProcessor, } case utils.MetaCDRs: // allow this method case utils.MetaRadauth: - radiusPass, err := agReq.Vars.FieldAsString([]string{utils.RadiusPassword}) - if err != nil { - return false, err - } - userPass, err := agReq.Vars.FieldAsString([]string{utils.UserPassword}) - if err != nil { - return false, err - } - if radiusPass != userPass { - agReq.CGRReply.Set([]string{utils.Error}, "Failed to authenticate request", false, false) - } + // To be implemented + //// radius pass will be taken from request directly + //radiusPass := "CGRateS.org" + //userPass, err := agReq.Vars.FieldAsString([]string{utils.UserPassword}) + //if err != nil { + // return false, err + //} + //if radiusPass != userPass { + // agReq.CGRReply.Set([]string{utils.Error}, "Failed to authenticate request", false, false) + //} } // separate request so we can capture the Terminate/Event also here if reqProcessor.Flags.HasKey(utils.MetaCDRs) { @@ -318,10 +327,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RequestProcessor, if err := agReq.SetFields(reqProcessor.ReplyFields); err != nil { return false, err } - utils.ToJSON(agReq.Reply) - if err := radReplyAppendAttributes(rply, agReq, reqProcessor.ReplyFields); err != nil { - return false, err - } + if reqProcessor.Flags.HasKey(utils.MetaLog) { utils.Logger.Info( fmt.Sprintf("<%s> LOG, Radius reply: %s", diff --git a/agents/radagent_it_test.go b/agents/radagent_it_test.go index f6806f7b9..2cfbb6320 100644 --- a/agents/radagent_it_test.go +++ b/agents/radagent_it_test.go @@ -25,6 +25,7 @@ import ( "net/rpc" "os/exec" "path" + "reflect" "testing" "time" @@ -199,7 +200,7 @@ func testRAitAuth(t *testing.T) { if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { t.Error(err) } - //if err := authReq.AddAVPWithName("Password", "CGRateS.org", ""); err != nil { + //if err := authReq.AddAVPWithName("User-Password", "CGRateS.org", ""); err != nil { // t.Error(err) //} if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { @@ -222,17 +223,16 @@ func testRAitAuth(t *testing.T) { } reply, err := raAuthClnt.SendRequest(authReq) if err != nil { - t.Error(err) + t.Fatal(err) + } + if reply.Code != radigo.AccessAccept { + t.Errorf("Received reply: %+v", reply) + } + if len(reply.AVPs) != 1 { // make sure max duration is received + t.Errorf("Received AVPs: %+v", reply.AVPs) + } else if !reflect.DeepEqual([]byte("session_max_time#10800"), reply.AVPs[0].RawValue) { + t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) } - fmt.Println(reply) - //if reply.Code != radigo.AccessAccept { - // t.Errorf("Received reply: %+v", reply) - //} - //if len(reply.AVPs) != 1 { // make sure max duration is received - // t.Errorf("Received AVPs: %+v", reply.AVPs) - //} else if !reflect.DeepEqual([]byte("session_max_time#10800"), reply.AVPs[0].RawValue) { - // t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) - //} } func testRAitAcctStart(t *testing.T) { diff --git a/data/conf/samples/radagent_internal/cgrates.json b/data/conf/samples/radagent_internal/cgrates.json index 2bfdb2eea..c6054e852 100644 --- a/data/conf/samples/radagent_internal/cgrates.json +++ b/data/conf/samples/radagent_internal/cgrates.json @@ -74,8 +74,8 @@ "request_fields":[ {"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call"}, {"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*constant", - "value": "*prepaid", "mandatory": true}, - {"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*variable", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", "value": "~*req.Acct-Session-Id;-;~*req.Sip-From-Tag", "mandatory": true}, {"tag": "Account", "path": "*cgreq.Account", "type": "*variable", "value": "~*req.User-Name", "mandatory": true}, @@ -97,19 +97,17 @@ }, { "id": "RadiusPAPAuth", - "filters": ["*string:~*vars.*radReqType:*radAuth","*exists:~*req.User-Password"], + "filters": ["*string:~*vars.*radReqType:*radAuth","*exists:~*req.User-Password:"], "flags": ["*radauth", "*log"], "request_fields":[ - {"tag": "UserPassword", "path": "*vars.UserPassword", "type": "*composed", + {"tag": "UserPassword", "path": "*vars.UserPassword", "type": "*variable", "value": "~*cgrep.Attributes.PasswordFromAttributes"}, - {"tag": "PasswordFromRadius", "path": "*vars.RadiusPassword", "type": "*composed", - "value": "~*req.Password"} ], "reply_fields":[ {"filters": ["*empty:~*cgrep.Error:"], "type": "*none", "blocker": true}, {"filters": ["*notempty:~*cgrep.Error:"], "type": "*removeall", "path": "*rep"}, {"tag": "Code", "path": "*rep.*radReplyCode", "type": "*constant", "value": "AccessReject"}, - {"tag": "ReplyMessage", "path": "*rep.Reply-Message", "type": "*composed", "value": "~*cgrep.Error"} + {"tag": "ReplyMessage", "path": "*rep.Reply-Message", "type": "*variable", "value": "~*cgrep.Error"} ] }, { @@ -165,7 +163,7 @@ {"tag": "RemoteAddr" , "path": "*cgreq.RemoteAddr", "type": "*remote_host"}, ], "reply_fields":[], - }, + }, ], },