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 (