diff --git a/agents/librad.go b/agents/librad.go index e08cfe455..e1ecf36fc 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -19,6 +19,7 @@ along with this program. If not, see package agents import ( + "crypto/md5" "fmt" "net" @@ -118,3 +119,55 @@ func (pk *radiusDP) AsNavigableMap([]*config.FCTemplate) ( func (pk *radiusDP) RemoteHost() net.Addr { return utils.NewNetAddr(pk.req.RemoteAddr().Network(), pk.req.RemoteAddr().String()) } + +//authReq is used to authorize a request +//if User-Password avp is present use PAP auth +//if CHAP-Password is presented use CHAP auth +func authReq(req *radigo.Packet, aReq *AgentRequest) (bool, error) { + // try to get UserPassword from Vars as slice of NMItems + nmItems, err := aReq.Vars.FieldAsInterface([]string{utils.UserPassword}) + if err != nil { + return false, err + } + userPassAvps := req.AttributesWithName("User-Password", utils.EmptyString) + chapAVPs := req.AttributesWithName("CHAP-Password", utils.EmptyString) + if len(userPassAvps) == 0 && len(chapAVPs) == 0 { + return false, fmt.Errorf("cannot find User-Password or CHAP-Password AVP in request") + } + if len(userPassAvps) != 0 { + if userPassAvps[0].StringValue != nmItems.([]*config.NMItem)[0].Data { + return false, nil + } + } else { + return checkAgainstCHAP([]byte(utils.IfaceAsString(nmItems.([]*config.NMItem)[0].Data)), + req.Authenticator[:], chapAVPs[0].RawValue), nil + } + return true, nil +} + +//checkAgainstCHAP receive the password as plaintext and verify against the chap challenge +func checkAgainstCHAP(password, authenticator, chapChallenge []byte) bool { + h := md5.New() + h.Write(chapChallenge[:1]) + h.Write(password) + h.Write(authenticator) + answer := h.Sum(nil) + if len(answer) != len(chapChallenge[1:]) { + return false + } + for i := range answer { + if answer[i] != chapChallenge[i+1] { + return false + } + } + return true +} + +//encodeChap is used in test to encode CHAP-Password raw value +func encodeChap(password, authenticator, chapIdent []byte) []byte { + h := md5.New() + h.Write(chapIdent) + h.Write(password) + h.Write(authenticator) + return h.Sum(nil) +} diff --git a/agents/radagent.go b/agents/radagent.go index 0c94b829c..c7f039f64 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -304,16 +304,9 @@ func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.R } case utils.MetaCDRs: // allow this method case utils.MetaRadauth: - // try to get UserPassword from Vars as slice of NMItems - nmItems, err := agReq.Vars.FieldAsInterface([]string{utils.UserPassword}) - if err != nil { + if ok, err := authReq(req, agReq); err != nil { return false, err - } - avps := req.AttributesWithName("User-Password", utils.EmptyString) - if len(avps) == 0 { - return false, fmt.Errorf("cannot find User-Password AVP in request") - } - if string(avps[0].RawValue) != nmItems.([]*config.NMItem)[0].Data { + } else if !ok { agReq.CGRReply.Set([]string{utils.Error}, "Failed to authenticate request", false, false) } } diff --git a/agents/radagent_it_test.go b/agents/radagent_it_test.go index 558a08b2c..7073ac030 100644 --- a/agents/radagent_it_test.go +++ b/agents/radagent_it_test.go @@ -21,6 +21,7 @@ along with this program. If not, see package agents import ( + "crypto/rand" "fmt" "net/rpc" "os/exec" @@ -50,8 +51,10 @@ var ( testRAitStartEngine, testRAitApierRpcConn, testRAitTPFromFolder, - testRAitAuthSuccess, - testRAitAuthFail, + testRAitAuthPAPSuccess, + testRAitAuthPAPFail, + testRAitAuthCHAPSuccess, + testRAitAuthCHAPFail, testRAitAcctStart, testRAitAcctStop, testRAitStopCgrEngine, @@ -192,7 +195,7 @@ func testRadiusitTPLoadData(t *testing.T) { } } -func testRAitAuthSuccess(t *testing.T) { +func testRAitAuthPAPSuccess(t *testing.T) { if raAuthClnt, err = radigo.NewClient("udp", "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil); err != nil { t.Fatal(err) } @@ -203,6 +206,8 @@ func testRAitAuthSuccess(t *testing.T) { if err := authReq.AddAVPWithName("User-Password", "CGRateSPassword1", ""); err != nil { t.Error(err) } + // encode the password as required so we can decode it properly + authReq.AVPs[1].RawValue = radigo.EncodePass([]byte("CGRateSPassword1"), []byte("CGRateS.org"), authReq.Authenticator[:]) if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { t.Error(err) } @@ -226,16 +231,16 @@ func testRAitAuthSuccess(t *testing.T) { t.Fatal(err) } if reply.Code != radigo.AccessAccept { - t.Errorf("Received reply: %+v", reply) + t.Errorf("Received reply: %+v", utils.ToJSON(reply)) } if len(reply.AVPs) != 1 { // make sure max duration is received - t.Errorf("Received AVPs: %+v", reply.AVPs) + t.Errorf("Received AVPs: %+v", utils.ToJSON(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 testRAitAuthFail(t *testing.T) { +func testRAitAuthPAPFail(t *testing.T) { if raAuthClnt, err = radigo.NewClient("udp", "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil); err != nil { t.Fatal(err) } @@ -246,6 +251,8 @@ func testRAitAuthFail(t *testing.T) { if err := authReq.AddAVPWithName("User-Password", "CGRateSPassword2", ""); err != nil { t.Error(err) } + // encode the password as required so we can decode it properly + authReq.AVPs[1].RawValue = radigo.EncodePass([]byte("CGRateSPassword2"), []byte("CGRateS.org"), authReq.Authenticator[:]) if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { t.Error(err) } @@ -273,7 +280,108 @@ func testRAitAuthFail(t *testing.T) { } if len(reply.AVPs) != 1 { // make sure max duration is received t.Errorf("Received AVPs: %+v", reply.AVPs) - } else if !reflect.DeepEqual([]byte("Failed to authenticate request"), reply.AVPs[0].RawValue) { + } else if !reflect.DeepEqual("Failed to authenticate request", string(reply.AVPs[0].RawValue)) { + t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) + } +} + +func testRAitAuthCHAPSuccess(t *testing.T) { + if raAuthClnt, err = radigo.NewClient("udp", "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil); err != nil { + t.Fatal(err) + } + authReq := raAuthClnt.NewRequest(radigo.AccessRequest, 1) // emulates Kamailio packet out of radius_load_caller_avps() + if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("CHAP-Password", "CGRateSPassword1", ""); err != nil { + t.Error(err) + } + // simulate encoding for CHAP-Password + chapIdent := make([]byte, 1) + rand.Read(chapIdent) + chapChallange := encodeChap([]byte("CGRateSPassword1"), authReq.Authenticator[:], chapIdent) + chapRawVal := make([]byte, 17) + copy(chapRawVal[:1], chapIdent) // copy the Ident + copy(chapRawVal[1:], chapChallange) // copy the challange that needs to be verify + authReq.AVPs[1].RawValue = chapRawVal + if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Sip-From-Tag", "51585361", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Event-Timestamp", "1497106115", ""); err != nil { + t.Error(err) + } + reply, err := raAuthClnt.SendRequest(authReq) + if err != nil { + t.Fatal(err) + } + if reply.Code != radigo.AccessAccept { + t.Errorf("Received reply: %+v", utils.ToJSON(reply)) + } + if len(reply.AVPs) != 1 { // make sure max duration is received + t.Errorf("Received AVPs: %+v", utils.ToJSON(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 testRAitAuthCHAPFail(t *testing.T) { + if raAuthClnt, err = radigo.NewClient("udp", "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil); err != nil { + t.Fatal(err) + } + authReq := raAuthClnt.NewRequest(radigo.AccessRequest, 1) // emulates Kamailio packet out of radius_load_caller_avps() + if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("CHAP-Password", "CGRateSPassword2", ""); err != nil { + t.Error(err) + } + chapIdent := make([]byte, 1) + rand.Read(chapIdent) + chapChallange := encodeChap([]byte("CGRateSPassword2"), authReq.Authenticator[:], chapIdent) + chapRawVal := make([]byte, 17) + copy(chapRawVal[:1], chapIdent) + copy(chapRawVal[1:], chapChallange) + authReq.AVPs[1].RawValue = chapRawVal + if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Sip-From-Tag", "51585361", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Event-Timestamp", "1497106115", ""); err != nil { + t.Error(err) + } + reply, err := raAuthClnt.SendRequest(authReq) + if err != nil { + t.Fatal(err) + } + if reply.Code != radigo.AccessReject { + 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("Failed to authenticate request", string(reply.AVPs[0].RawValue)) { t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) } } diff --git a/go.mod b/go.mod index cc348dcb7..81a97c5ae 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,8 @@ module github.com/cgrates/cgrates go 1.14 -// replace github.com/cgrates/radigo => /home/dan/go/src/github.com/cgrates/radigo +replace github.com/cgrates/radigo => ../radigo + // replace github.com/cgrates/rpcclient => ../rpcclient require (