blob: 634f015c214afb7f6f974c340d07ea3b5048dc40 [file] [log] [blame]
khenaidoo106c61a2021-08-11 18:05:46 -04001package client
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "io"
8 "net"
9 "strings"
10 "time"
11
12 "github.com/jcmturner/gokrb5/v8/iana/errorcode"
13 "github.com/jcmturner/gokrb5/v8/messages"
14)
15
16// SendToKDC performs network actions to send data to the KDC.
17func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) {
18 var rb []byte
19 if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
20 //1 means we should always use TCP
21 rb, errtcp := cl.sendKDCTCP(realm, b)
22 if errtcp != nil {
23 if e, ok := errtcp.(messages.KRBError); ok {
24 return rb, e
25 }
26 return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
27 }
28 return rb, nil
29 }
30 if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
31 //Try UDP first, TCP second
32 rb, errudp := cl.sendKDCUDP(realm, b)
33 if errudp != nil {
34 if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
35 // Got a KRBError from KDC
36 // If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
37 return rb, e
38 }
39 // Try TCP
40 r, errtcp := cl.sendKDCTCP(realm, b)
41 if errtcp != nil {
42 if e, ok := errtcp.(messages.KRBError); ok {
43 // Got a KRBError
44 return r, e
45 }
46 return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
47 }
48 rb = r
49 }
50 return rb, nil
51 }
52 //Try TCP first, UDP second
53 rb, errtcp := cl.sendKDCTCP(realm, b)
54 if errtcp != nil {
55 if e, ok := errtcp.(messages.KRBError); ok {
56 // Got a KRBError from KDC so returning and not trying UDP.
57 return rb, e
58 }
59 rb, errudp := cl.sendKDCUDP(realm, b)
60 if errudp != nil {
61 if e, ok := errudp.(messages.KRBError); ok {
62 // Got a KRBError
63 return rb, e
64 }
65 return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
66 }
67 }
68 return rb, nil
69}
70
71// sendKDCUDP sends bytes to the KDC via UDP.
72func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
73 var r []byte
74 _, kdcs, err := cl.Config.GetKDCs(realm, false)
75 if err != nil {
76 return r, err
77 }
78 r, err = dialSendUDP(kdcs, b)
79 if err != nil {
80 return r, err
81 }
82 return checkForKRBError(r)
83}
84
85// dialSendUDP establishes a UDP connection to a KDC.
86func dialSendUDP(kdcs map[int]string, b []byte) ([]byte, error) {
87 var errs []string
88 for i := 1; i <= len(kdcs); i++ {
89 udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i])
90 if err != nil {
91 errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
92 continue
93 }
94
95 conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second)
96 if err != nil {
97 errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
98 continue
99 }
100 if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
101 errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
102 continue
103 }
104 // conn is guaranteed to be a UDPConn
105 rb, err := sendUDP(conn.(*net.UDPConn), b)
106 if err != nil {
107 errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
108 continue
109 }
110 return rb, nil
111 }
112 return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
113}
114
115// sendUDP sends bytes to connection over UDP.
116func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
117 var r []byte
118 defer conn.Close()
119 _, err := conn.Write(b)
120 if err != nil {
121 return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
122 }
123 udpbuf := make([]byte, 4096)
124 n, _, err := conn.ReadFrom(udpbuf)
125 r = udpbuf[:n]
126 if err != nil {
127 return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
128 }
129 if len(r) < 1 {
130 return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
131 }
132 return r, nil
133}
134
135// sendKDCTCP sends bytes to the KDC via TCP.
136func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
137 var r []byte
138 _, kdcs, err := cl.Config.GetKDCs(realm, true)
139 if err != nil {
140 return r, err
141 }
142 r, err = dialSendTCP(kdcs, b)
143 if err != nil {
144 return r, err
145 }
146 return checkForKRBError(r)
147}
148
149// dialKDCTCP establishes a TCP connection to a KDC.
150func dialSendTCP(kdcs map[int]string, b []byte) ([]byte, error) {
151 var errs []string
152 for i := 1; i <= len(kdcs); i++ {
153 tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])
154 if err != nil {
155 errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
156 continue
157 }
158
159 conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second)
160 if err != nil {
161 errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
162 continue
163 }
164 if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
165 errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
166 continue
167 }
168 // conn is guaranteed to be a TCPConn
169 rb, err := sendTCP(conn.(*net.TCPConn), b)
170 if err != nil {
171 errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
172 continue
173 }
174 return rb, nil
175 }
176 return nil, errors.New("error in getting a TCP connection to any of the KDCs")
177}
178
179// sendTCP sends bytes to connection over TCP.
180func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
181 defer conn.Close()
182 var r []byte
183 // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
184 hb := make([]byte, 4, 4)
185 binary.BigEndian.PutUint32(hb, uint32(len(b)))
186 b = append(hb, b...)
187
188 _, err := conn.Write(b)
189 if err != nil {
190 return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
191 }
192
193 sh := make([]byte, 4, 4)
194 _, err = conn.Read(sh)
195 if err != nil {
196 return r, fmt.Errorf("error reading response size header: %v", err)
197 }
198 s := binary.BigEndian.Uint32(sh)
199
200 rb := make([]byte, s, s)
201 _, err = io.ReadFull(conn, rb)
202 if err != nil {
203 return r, fmt.Errorf("error reading response: %v", err)
204 }
205 if len(rb) < 1 {
206 return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
207 }
208 return rb, nil
209}
210
211// checkForKRBError checks if the response bytes from the KDC are a KRBError.
212func checkForKRBError(b []byte) ([]byte, error) {
213 var KRBErr messages.KRBError
214 if err := KRBErr.Unmarshal(b); err == nil {
215 return b, KRBErr
216 }
217 return b, nil
218}