1package config
2
3import (
4	"fmt"
5	"math/rand"
6	"net"
7	"strconv"
8	"strings"
9
10	"gopkg.in/jcmturner/dnsutils.v1"
11)
12
13// GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order.
14func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) {
15	if realm == "" {
16		realm = c.LibDefaults.DefaultRealm
17	}
18	kdcs := make(map[int]string)
19	var count int
20
21	// Get the KDCs from the krb5.conf.
22	var ks []string
23	for _, r := range c.Realms {
24		if r.Realm != realm {
25			continue
26		}
27		ks = r.KDC
28	}
29	count = len(ks)
30
31	if count > 0 {
32		// Order the kdcs randomly for preference.
33		kdcs = randServOrder(ks)
34		return count, kdcs, nil
35	}
36
37	if !c.LibDefaults.DNSLookupKDC {
38		return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
39	}
40
41	// Use DNS to resolve kerberos SRV records.
42	proto := "udp"
43	if tcp {
44		proto = "tcp"
45	}
46	index, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
47	if err != nil {
48		return count, kdcs, err
49	}
50	if len(addrs) < 1 {
51		return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
52	}
53	count = index
54	for k, v := range addrs {
55		kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
56	}
57	return count, kdcs, nil
58}
59
60// GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order.
61// https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section
62func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) {
63	kdcs := make(map[int]string)
64	var count int
65
66	// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
67	if c.LibDefaults.DNSLookupKDC {
68		proto := "udp"
69		if tcp {
70			proto = "tcp"
71		}
72		c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm)
73		if err != nil {
74			return count, kdcs, err
75		}
76		if c < 1 {
77			c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm)
78			if err != nil {
79				return count, kdcs, err
80			}
81		}
82		if len(addrs) < 1 {
83			return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm)
84		}
85		count = c
86		for k, v := range addrs {
87			kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
88		}
89	} else {
90		// Get the KDCs from the krb5.conf an order them randomly for preference.
91		var ks []string
92		var ka []string
93		for _, r := range c.Realms {
94			if r.Realm == realm {
95				ks = r.KPasswdServer
96				ka = r.AdminServer
97				break
98			}
99		}
100		if len(ks) < 1 {
101			for _, k := range ka {
102				h, _, err := net.SplitHostPort(k)
103				if err != nil {
104					continue
105				}
106				ks = append(ks, h+":464")
107			}
108		}
109		count = len(ks)
110		if count < 1 {
111			return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm)
112		}
113		kdcs = randServOrder(ks)
114	}
115	return count, kdcs, nil
116}
117
118func randServOrder(ks []string) map[int]string {
119	kdcs := make(map[int]string)
120	count := len(ks)
121	i := 1
122	if count > 1 {
123		l := len(ks)
124		for l > 0 {
125			ri := rand.Intn(l)
126			kdcs[i] = ks[ri]
127			if l > 1 {
128				// Remove the entry from the source slice by swapping with the last entry and truncating
129				ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
130				ks = ks[:len(ks)-1]
131				l = len(ks)
132			} else {
133				l = 0
134			}
135			i++
136		}
137	} else {
138		kdcs[i] = ks[0]
139	}
140	return kdcs
141}
142