|
|
@@ -3,54 +3,93 @@ package client
|
|
|
import (
|
|
|
"bytes"
|
|
|
"encoding/binary"
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
+ "gopkg.in/jcmturner/dnsutils.v1"
|
|
|
"io"
|
|
|
+ "gopkg.in/jcmturner/gokrb5.v2/iana/errorcode"
|
|
|
+ "gopkg.in/jcmturner/gokrb5.v2/messages"
|
|
|
"math/rand"
|
|
|
"net"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
"time"
|
|
|
-
|
|
|
- "gopkg.in/jcmturner/gokrb5.v2/iana/errorcode"
|
|
|
- "gopkg.in/jcmturner/gokrb5.v2/messages"
|
|
|
)
|
|
|
|
|
|
-// SendToKDC performs network actions to send data to the KDC.
|
|
|
-func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
|
|
|
- var rb []byte
|
|
|
- var kdcs []string
|
|
|
- for _, r := range cl.Config.Realms {
|
|
|
- if r.Realm == realm {
|
|
|
- kdcs = r.KDC
|
|
|
- break
|
|
|
+func (cl *Client) resolveKDC(realm string, tcp bool) (int, map[int]string, error) {
|
|
|
+ kdcs := make(map[int]string)
|
|
|
+ var count int
|
|
|
+
|
|
|
+ // Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
|
|
|
+ if cl.Config.LibDefaults.DNSLookupKDC {
|
|
|
+ proto := "udp"
|
|
|
+ if tcp {
|
|
|
+ proto = "tcp"
|
|
|
+ }
|
|
|
+ c, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
|
|
|
+ if err != nil {
|
|
|
+ return count, kdcs, err
|
|
|
+ }
|
|
|
+ if len(addrs) < 1 {
|
|
|
+ return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
|
|
|
+ }
|
|
|
+ count = c
|
|
|
+ for k, v := range addrs {
|
|
|
+ kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
|
|
|
}
|
|
|
- }
|
|
|
- if len(kdcs) < 1 {
|
|
|
- return rb, fmt.Errorf("No KDCs defined in configuration for realm: %v", cl.Config.LibDefaults.DefaultRealm)
|
|
|
- }
|
|
|
- var kdc string
|
|
|
- if len(kdcs) > 1 {
|
|
|
- //Select one of the KDCs at random
|
|
|
- kdc = kdcs[rand.Intn(len(kdcs))]
|
|
|
} else {
|
|
|
- kdc = kdcs[0]
|
|
|
+ // Get the KDCs from the krb5.conf an order them randomly for preference.
|
|
|
+ var ks []string
|
|
|
+ for _, r := range cl.Config.Realms {
|
|
|
+ if r.Realm == realm {
|
|
|
+ ks = r.KDC
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ count = len(ks)
|
|
|
+ if count < 1 {
|
|
|
+ return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
|
|
|
+ }
|
|
|
+ i := 1
|
|
|
+ if count > 1 {
|
|
|
+ l := len(ks)
|
|
|
+ for l > 0 {
|
|
|
+ ri := rand.Intn(l)
|
|
|
+ kdcs[i] = ks[ri]
|
|
|
+ if l > 1 {
|
|
|
+ // Remove the entry from the source slice by swapping with the last entry and truncating
|
|
|
+ ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
|
|
|
+ ks = ks[:len(ks)-1]
|
|
|
+ l = len(ks)
|
|
|
+ } else {
|
|
|
+ l = 0
|
|
|
+ }
|
|
|
+ i += 1
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ kdcs[i] = ks[0]
|
|
|
+ }
|
|
|
}
|
|
|
+ return count, kdcs, nil
|
|
|
+}
|
|
|
|
|
|
+// SendToKDC performs network actions to send data to the KDC.
|
|
|
+func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
|
|
|
+ var rb []byte
|
|
|
if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
|
|
|
//1 means we should always use TCP
|
|
|
- rb, errtcp := sendTCP(kdc, b)
|
|
|
+ rb, errtcp := cl.sendTCP(realm, b)
|
|
|
if errtcp != nil {
|
|
|
if e, ok := errtcp.(messages.KRBError); ok {
|
|
|
return rb, e
|
|
|
}
|
|
|
- return rb, fmt.Errorf("Failed to communicate with KDC %v via TCP (%v)", kdc, errtcp)
|
|
|
- }
|
|
|
- if len(rb) < 1 {
|
|
|
- return rb, fmt.Errorf("No response data from KDC %v", kdc)
|
|
|
+ return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
|
|
|
}
|
|
|
return rb, nil
|
|
|
}
|
|
|
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
|
|
|
//Try UDP first, TCP second
|
|
|
- rb, errudp := sendUDP(kdc, b)
|
|
|
+ rb, errudp := cl.sendUDP(realm, b)
|
|
|
if errudp != nil {
|
|
|
if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
|
|
|
// Got a KRBError from KDC
|
|
|
@@ -58,82 +97,115 @@ func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
|
|
|
return rb, e
|
|
|
}
|
|
|
// Try TCP
|
|
|
- r, errtcp := sendTCP(kdc, b)
|
|
|
+ r, errtcp := cl.sendTCP(realm, b)
|
|
|
if errtcp != nil {
|
|
|
if e, ok := errtcp.(messages.KRBError); ok {
|
|
|
// Got a KRBError
|
|
|
return r, e
|
|
|
}
|
|
|
- return r, fmt.Errorf("Failed to communicate with KDC %v. Attempts made with UDP (%v) and then TCP (%v)", kdc, errudp, errtcp)
|
|
|
+ return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
|
|
|
}
|
|
|
rb = r
|
|
|
}
|
|
|
- if len(rb) < 1 {
|
|
|
- return rb, fmt.Errorf("No response data from KDC %v", kdc)
|
|
|
- }
|
|
|
return rb, nil
|
|
|
}
|
|
|
//Try TCP first, UDP second
|
|
|
- rb, errtcp := sendTCP(kdc, b)
|
|
|
+ rb, errtcp := cl.sendTCP(realm, b)
|
|
|
if errtcp != nil {
|
|
|
if e, ok := errtcp.(messages.KRBError); ok {
|
|
|
// Got a KRBError from KDC so returning and not trying UDP.
|
|
|
return rb, e
|
|
|
}
|
|
|
- rb, errudp := sendUDP(kdc, b)
|
|
|
+ rb, errudp := cl.sendUDP(realm, b)
|
|
|
if errudp != nil {
|
|
|
if e, ok := errudp.(messages.KRBError); ok {
|
|
|
// Got a KRBError
|
|
|
return rb, e
|
|
|
}
|
|
|
- return rb, fmt.Errorf("Failed to communicate with KDC %v. Attempts made with TCP (%v) and then UDP (%v)", kdc, errtcp, errudp)
|
|
|
+ return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
|
|
|
}
|
|
|
}
|
|
|
- if len(rb) < 1 {
|
|
|
- return rb, fmt.Errorf("No response data from KDC %v", kdc)
|
|
|
- }
|
|
|
return rb, nil
|
|
|
}
|
|
|
|
|
|
+func dialKDCUDP(count int, kdcs map[int]string) (conn *net.UDPConn, err error) {
|
|
|
+ i := 1
|
|
|
+ for i <= count {
|
|
|
+ udpAddr, e := net.ResolveUDPAddr("udp", kdcs[i])
|
|
|
+ if e != nil {
|
|
|
+ err = fmt.Errorf("error resolving KDC address: %v", e)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ conn, err = net.DialUDP("udp", nil, udpAddr)
|
|
|
+ if err == nil {
|
|
|
+ conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ i += 1
|
|
|
+ }
|
|
|
+ err = errors.New("error in getting a UDP connection to any of the KDCs")
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
|
|
|
+ i := 1
|
|
|
+ for i <= count {
|
|
|
+ tcpAddr, e := net.ResolveTCPAddr("tcp", kdcs[i])
|
|
|
+ if e != nil {
|
|
|
+ err = fmt.Errorf("error resolving KDC address: %v", e)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ conn, err = net.DialTCP("tcp", nil, tcpAddr)
|
|
|
+ if err == nil {
|
|
|
+ conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ i += 1
|
|
|
+ }
|
|
|
+ err = errors.New("error in getting a TCP connection to any of the KDCs")
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
// Send the bytes to the KDC over UDP.
|
|
|
-func sendUDP(kdc string, b []byte) ([]byte, error) {
|
|
|
+func (cl *Client) sendUDP(realm string, b []byte) ([]byte, error) {
|
|
|
var r []byte
|
|
|
- udpAddr, err := net.ResolveUDPAddr("udp", kdc)
|
|
|
+ count, kdcs, err := cl.resolveKDC(realm, false)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error resolving KDC address: %v", err)
|
|
|
+ return r, err
|
|
|
}
|
|
|
- conn, err := net.DialUDP("udp", nil, udpAddr)
|
|
|
+ conn, err := dialKDCUDP(count, kdcs)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error establishing connection to KDC: %v", err)
|
|
|
+ return r, err
|
|
|
}
|
|
|
defer conn.Close()
|
|
|
- conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
|
|
|
_, err = conn.Write(b)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error sending to KDC: %v", err)
|
|
|
+ return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
|
|
|
}
|
|
|
udpbuf := make([]byte, 4096)
|
|
|
n, _, err := conn.ReadFrom(udpbuf)
|
|
|
r = udpbuf[:n]
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Sending over UDP failed: %v", err)
|
|
|
+ return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
|
|
|
+ }
|
|
|
+ if len(r) < 1 {
|
|
|
+ return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
|
|
|
}
|
|
|
return checkForKRBError(r)
|
|
|
}
|
|
|
|
|
|
// Send the bytes to the KDC over TCP.
|
|
|
-func sendTCP(kdc string, b []byte) ([]byte, error) {
|
|
|
+func (cl *Client) sendTCP(realm string, b []byte) ([]byte, error) {
|
|
|
var r []byte
|
|
|
- tcpAddr, err := net.ResolveTCPAddr("tcp", kdc)
|
|
|
+ count, kdcs, err := cl.resolveKDC(realm, true)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error resolving KDC address: %v", err)
|
|
|
+ return r, err
|
|
|
}
|
|
|
- conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
|
|
+ conn, err := dialKDCTCP(count, kdcs)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error establishing connection to KDC: %v", err)
|
|
|
+ return r, err
|
|
|
}
|
|
|
defer conn.Close()
|
|
|
- conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
|
|
|
|
|
|
/*
|
|
|
RFC https://tools.ietf.org/html/rfc4120#section-7.2.2
|
|
|
@@ -153,7 +225,7 @@ func sendTCP(kdc string, b []byte) ([]byte, error) {
|
|
|
|
|
|
_, err = conn.Write(b)
|
|
|
if err != nil {
|
|
|
- return r, fmt.Errorf("Error sending to KDC: %v", err)
|
|
|
+ return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
|
|
|
}
|
|
|
|
|
|
sh := make([]byte, 4, 4)
|
|
|
@@ -168,7 +240,9 @@ func sendTCP(kdc string, b []byte) ([]byte, error) {
|
|
|
if err != nil {
|
|
|
return r, fmt.Errorf("error reading response: %v", err)
|
|
|
}
|
|
|
-
|
|
|
+ if len(rb) < 1 {
|
|
|
+ return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
|
|
|
+ }
|
|
|
return checkForKRBError(rb)
|
|
|
}
|
|
|
|