SEBA-902 single-olt tests;
Pin protoc-gen-go to 1.3.2 to resolve compatibility issue;
Run go mod tidy / go mod vendor on importer;
Add Go Module support to demotest
Change-Id: Ifde824fc9a6317b0adc1e12bea54ee1f9b788906
diff --git a/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/error.go b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/error.go
new file mode 100644
index 0000000..1fbda51
--- /dev/null
+++ b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/error.go
@@ -0,0 +1,30 @@
+package config
+
+import "fmt"
+
+// UnsupportedDirective error.
+type UnsupportedDirective struct {
+ text string
+}
+
+// Error implements the error interface for unsupported directives.
+func (e UnsupportedDirective) Error() string {
+ return e.text
+}
+
+// Invalid config error.
+type Invalid struct {
+ text string
+}
+
+// Error implements the error interface for invalid config error.
+func (e Invalid) Error() string {
+ return e.text
+}
+
+// InvalidErrorf creates a new Invalid error.
+func InvalidErrorf(format string, a ...interface{}) Invalid {
+ return Invalid{
+ text: fmt.Sprintf("invalid krb5 config "+format, a...),
+ }
+}
diff --git a/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/hosts.go b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/hosts.go
new file mode 100644
index 0000000..a67989f
--- /dev/null
+++ b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/hosts.go
@@ -0,0 +1,141 @@
+package config
+
+import (
+ "fmt"
+ "math/rand"
+ "net"
+ "strconv"
+ "strings"
+
+ "gopkg.in/jcmturner/dnsutils.v1"
+)
+
+// GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order.
+func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) {
+ if realm == "" {
+ realm = c.LibDefaults.DefaultRealm
+ }
+ kdcs := make(map[int]string)
+ var count int
+
+ // Get the KDCs from the krb5.conf.
+ var ks []string
+ for _, r := range c.Realms {
+ if r.Realm != realm {
+ continue
+ }
+ ks = r.KDC
+ }
+ count = len(ks)
+
+ if count > 0 {
+ // Order the kdcs randomly for preference.
+ kdcs = randServOrder(ks)
+ return count, kdcs, nil
+ }
+
+ if !c.LibDefaults.DNSLookupKDC {
+ return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
+ }
+
+ // Use DNS to resolve kerberos SRV records.
+ proto := "udp"
+ if tcp {
+ proto = "tcp"
+ }
+ index, 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 = index
+ for k, v := range addrs {
+ kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
+ }
+ return count, kdcs, nil
+}
+
+// GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order.
+// https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section
+func (c *Config) GetKpasswdServers(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 c.LibDefaults.DNSLookupKDC {
+ proto := "udp"
+ if tcp {
+ proto = "tcp"
+ }
+ c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm)
+ if err != nil {
+ return count, kdcs, err
+ }
+ if c < 1 {
+ c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm)
+ if err != nil {
+ return count, kdcs, err
+ }
+ }
+ if len(addrs) < 1 {
+ return count, kdcs, fmt.Errorf("no kpasswd or kadmin 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))
+ }
+ } else {
+ // Get the KDCs from the krb5.conf an order them randomly for preference.
+ var ks []string
+ var ka []string
+ for _, r := range c.Realms {
+ if r.Realm == realm {
+ ks = r.KPasswdServer
+ ka = r.AdminServer
+ break
+ }
+ }
+ if len(ks) < 1 {
+ for _, k := range ka {
+ h, _, err := net.SplitHostPort(k)
+ if err != nil {
+ continue
+ }
+ ks = append(ks, h+":464")
+ }
+ }
+ count = len(ks)
+ if count < 1 {
+ return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm)
+ }
+ kdcs = randServOrder(ks)
+ }
+ return count, kdcs, nil
+}
+
+func randServOrder(ks []string) map[int]string {
+ kdcs := make(map[int]string)
+ count := len(ks)
+ 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++
+ }
+ } else {
+ kdcs[i] = ks[0]
+ }
+ return kdcs
+}
diff --git a/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/krb5conf.go b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/krb5conf.go
new file mode 100644
index 0000000..8efe92d
--- /dev/null
+++ b/demo_test/vendor/gopkg.in/jcmturner/gokrb5.v7/config/krb5conf.go
@@ -0,0 +1,726 @@
+// Package config implements KRB5 client and service configuration as described at https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html
+package config
+
+import (
+ "bufio"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/user"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/jcmturner/gofork/encoding/asn1"
+ "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+)
+
+// Config represents the KRB5 configuration.
+type Config struct {
+ LibDefaults LibDefaults
+ Realms []Realm
+ DomainRealm DomainRealm
+ //CaPaths
+ //AppDefaults
+ //Plugins
+}
+
+// WeakETypeList is a list of encryption types that have been deemed weak.
+const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des"
+
+// NewConfig creates a new config struct instance.
+func NewConfig() *Config {
+ d := make(DomainRealm)
+ return &Config{
+ LibDefaults: newLibDefaults(),
+ DomainRealm: d,
+ }
+}
+
+// LibDefaults represents the [libdefaults] section of the configuration.
+type LibDefaults struct {
+ AllowWeakCrypto bool //default false
+ // ap_req_checksum_type int //unlikely to support this
+ Canonicalize bool //default false
+ CCacheType int //default is 4. unlikely to implement older
+ Clockskew time.Duration //max allowed skew in seconds, default 300
+ //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory
+ DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab
+ DefaultKeytabName string //default /etc/krb5.keytab
+ DefaultRealm string
+ DefaultTGSEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+ DefaultTktEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+ DefaultTGSEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+ DefaultTktEnctypeIDs []int32 //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+ DNSCanonicalizeHostname bool //default true
+ DNSLookupKDC bool //default false
+ DNSLookupRealm bool
+ ExtraAddresses []net.IP //Not implementing yet
+ Forwardable bool //default false
+ IgnoreAcceptorHostname bool //default false
+ K5LoginAuthoritative bool //default false
+ K5LoginDirectory string //default user's home directory. Must be owned by the user or root
+ KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK)
+ KDCTimeSync int //default 1
+ //kdc_req_checksum_type int //unlikely to implement as for very old KDCs
+ NoAddresses bool //default true
+ PermittedEnctypes []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+ PermittedEnctypeIDs []int32
+ //plugin_base_dir string //not supporting plugins
+ PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported
+ Proxiable bool //default false
+ RDNS bool //default true
+ RealmTryDomains int //default -1
+ RenewLifetime time.Duration //default 0
+ SafeChecksumType int //default 8
+ TicketLifetime time.Duration //default 1 day
+ UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700.
+ VerifyAPReqNofail bool //default false
+}
+
+// Create a new LibDefaults struct.
+func newLibDefaults() LibDefaults {
+ uid := "0"
+ var hdir string
+ usr, _ := user.Current()
+ if usr != nil {
+ uid = usr.Uid
+ hdir = usr.HomeDir
+ }
+ opts := asn1.BitString{}
+ opts.Bytes, _ = hex.DecodeString("00000010")
+ opts.BitLength = len(opts.Bytes) * 8
+ return LibDefaults{
+ CCacheType: 4,
+ Clockskew: time.Duration(300) * time.Second,
+ DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid),
+ DefaultKeytabName: "/etc/krb5.keytab",
+ DefaultTGSEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+ DefaultTktEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+ DNSCanonicalizeHostname: true,
+ K5LoginDirectory: hdir,
+ KDCDefaultOptions: opts,
+ KDCTimeSync: 1,
+ NoAddresses: true,
+ PermittedEnctypes: []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+ RDNS: true,
+ RealmTryDomains: -1,
+ SafeChecksumType: 8,
+ TicketLifetime: time.Duration(24) * time.Hour,
+ UDPPreferenceLimit: 1465,
+ PreferredPreauthTypes: []int{17, 16, 15, 14},
+ }
+}
+
+// Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct.
+func (l *LibDefaults) parseLines(lines []string) error {
+ for _, line := range lines {
+ //Remove comments after the values
+ if idx := strings.IndexAny(line, "#;"); idx != -1 {
+ line = line[:idx]
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ if !strings.Contains(line, "=") {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+
+ p := strings.Split(line, "=")
+ key := strings.TrimSpace(strings.ToLower(p[0]))
+ switch key {
+ case "allow_weak_crypto":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.AllowWeakCrypto = v
+ case "canonicalize":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.Canonicalize = v
+ case "ccache_type":
+ p[1] = strings.TrimSpace(p[1])
+ v, err := strconv.ParseUint(p[1], 10, 32)
+ if err != nil || v < 0 || v > 4 {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+ l.CCacheType = int(v)
+ case "clockskew":
+ d, err := parseDuration(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.Clockskew = d
+ case "default_client_keytab_name":
+ l.DefaultClientKeytabName = strings.TrimSpace(p[1])
+ case "default_keytab_name":
+ l.DefaultKeytabName = strings.TrimSpace(p[1])
+ case "default_realm":
+ l.DefaultRealm = strings.TrimSpace(p[1])
+ case "default_tgs_enctypes":
+ l.DefaultTGSEnctypes = strings.Fields(p[1])
+ case "default_tkt_enctypes":
+ l.DefaultTktEnctypes = strings.Fields(p[1])
+ case "dns_canonicalize_hostname":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.DNSCanonicalizeHostname = v
+ case "dns_lookup_kdc":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.DNSLookupKDC = v
+ case "dns_lookup_realm":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.DNSLookupRealm = v
+ case "extra_addresses":
+ ipStr := strings.TrimSpace(p[1])
+ for _, ip := range strings.Split(ipStr, ",") {
+ if eip := net.ParseIP(ip); eip != nil {
+ l.ExtraAddresses = append(l.ExtraAddresses, eip)
+ }
+ }
+ case "forwardable":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.Forwardable = v
+ case "ignore_acceptor_hostname":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.IgnoreAcceptorHostname = v
+ case "k5login_authoritative":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.K5LoginAuthoritative = v
+ case "k5login_directory":
+ l.K5LoginDirectory = strings.TrimSpace(p[1])
+ case "kdc_default_options":
+ v := strings.TrimSpace(p[1])
+ v = strings.Replace(v, "0x", "", -1)
+ b, err := hex.DecodeString(v)
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.KDCDefaultOptions.Bytes = b
+ l.KDCDefaultOptions.BitLength = len(b) * 8
+ case "kdc_timesync":
+ p[1] = strings.TrimSpace(p[1])
+ v, err := strconv.ParseInt(p[1], 10, 32)
+ if err != nil || v < 0 {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+ l.KDCTimeSync = int(v)
+ case "noaddresses":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.NoAddresses = v
+ case "permitted_enctypes":
+ l.PermittedEnctypes = strings.Fields(p[1])
+ case "preferred_preauth_types":
+ p[1] = strings.TrimSpace(p[1])
+ t := strings.Split(p[1], ",")
+ var v []int
+ for _, s := range t {
+ i, err := strconv.ParseInt(s, 10, 32)
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ v = append(v, int(i))
+ }
+ l.PreferredPreauthTypes = v
+ case "proxiable":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.Proxiable = v
+ case "rdns":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.RDNS = v
+ case "realm_try_domains":
+ p[1] = strings.TrimSpace(p[1])
+ v, err := strconv.ParseInt(p[1], 10, 32)
+ if err != nil || v < -1 {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+ l.RealmTryDomains = int(v)
+ case "renew_lifetime":
+ d, err := parseDuration(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.RenewLifetime = d
+ case "safe_checksum_type":
+ p[1] = strings.TrimSpace(p[1])
+ v, err := strconv.ParseInt(p[1], 10, 32)
+ if err != nil || v < 0 {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+ l.SafeChecksumType = int(v)
+ case "ticket_lifetime":
+ d, err := parseDuration(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.TicketLifetime = d
+ case "udp_preference_limit":
+ p[1] = strings.TrimSpace(p[1])
+ v, err := strconv.ParseUint(p[1], 10, 32)
+ if err != nil || v > 32700 {
+ return InvalidErrorf("libdefaults section line (%s)", line)
+ }
+ l.UDPPreferenceLimit = int(v)
+ case "verify_ap_req_nofail":
+ v, err := parseBoolean(p[1])
+ if err != nil {
+ return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+ }
+ l.VerifyAPReqNofail = v
+ default:
+ //Ignore the line
+ continue
+ }
+ }
+ l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
+ l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
+ l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
+ return nil
+}
+
+// Realm represents an entry in the [realms] section of the configuration.
+type Realm struct {
+ Realm string
+ AdminServer []string
+ //auth_to_local //Not implementing for now
+ //auth_to_local_names //Not implementing for now
+ DefaultDomain string
+ KDC []string
+ KPasswdServer []string //default admin_server:464
+ MasterKDC []string
+}
+
+// Parse the lines of a [realms] entry into the Realm struct.
+func (r *Realm) parseLines(name string, lines []string) (err error) {
+ r.Realm = name
+ var adminServerFinal bool
+ var KDCFinal bool
+ var kpasswdServerFinal bool
+ var masterKDCFinal bool
+ var ignore bool
+ var c int // counts the depth of blocks within brackets { }
+ for _, line := range lines {
+ if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
+ continue
+ }
+ //Remove comments after the values
+ if idx := strings.IndexAny(line, "#;"); idx != -1 {
+ line = line[:idx]
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ if !strings.Contains(line, "=") && !strings.Contains(line, "}") {
+ return InvalidErrorf("realms section line (%s)", line)
+ }
+ if strings.Contains(line, "v4_") {
+ ignore = true
+ err = UnsupportedDirective{"v4 configurations are not supported"}
+ }
+ if strings.Contains(line, "{") {
+ c++
+ if ignore {
+ continue
+ }
+ }
+ if strings.Contains(line, "}") {
+ c--
+ if c < 0 {
+ return InvalidErrorf("unpaired curly brackets")
+ }
+ if ignore {
+ if c < 1 {
+ c = 0
+ ignore = false
+ }
+ continue
+ }
+ }
+
+ p := strings.Split(line, "=")
+ key := strings.TrimSpace(strings.ToLower(p[0]))
+ v := strings.TrimSpace(p[1])
+ switch key {
+ case "admin_server":
+ appendUntilFinal(&r.AdminServer, v, &adminServerFinal)
+ case "default_domain":
+ r.DefaultDomain = v
+ case "kdc":
+ if !strings.Contains(v, ":") {
+ // No port number specified default to 88
+ if strings.HasSuffix(v, `*`) {
+ v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*"
+ } else {
+ v = strings.TrimSpace(v) + ":88"
+ }
+ }
+ appendUntilFinal(&r.KDC, v, &KDCFinal)
+ case "kpasswd_server":
+ appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal)
+ case "master_kdc":
+ appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal)
+ default:
+ //Ignore the line
+ continue
+ }
+ }
+ //default for Kpasswd_server = admin_server:464
+ if len(r.KPasswdServer) < 1 {
+ for _, a := range r.AdminServer {
+ s := strings.Split(a, ":")
+ r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
+ }
+ }
+ return
+}
+
+// Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
+func parseRealms(lines []string) (realms []Realm, err error) {
+ var name string
+ var start int
+ var c int
+ for i, l := range lines {
+ //Remove comments after the values
+ if idx := strings.IndexAny(l, "#;"); idx != -1 {
+ l = l[:idx]
+ }
+ l = strings.TrimSpace(l)
+ if l == "" {
+ continue
+ }
+ //if strings.Contains(l, "v4_") {
+ // return nil, errors.New("v4 configurations are not supported in Realms section")
+ //}
+ if strings.Contains(l, "{") {
+ c++
+ if !strings.Contains(l, "=") {
+ return nil, fmt.Errorf("realm configuration line invalid: %s", l)
+ }
+ if c == 1 {
+ start = i
+ p := strings.Split(l, "=")
+ name = strings.TrimSpace(p[0])
+ }
+ }
+ if strings.Contains(l, "}") {
+ if c < 1 {
+ // but not started a block!!!
+ return nil, errors.New("invalid Realms section in configuration")
+ }
+ c--
+ if c == 0 {
+ var r Realm
+ e := r.parseLines(name, lines[start+1:i])
+ if e != nil {
+ if _, ok := e.(UnsupportedDirective); !ok {
+ err = e
+ return
+ }
+ err = e
+ }
+ realms = append(realms, r)
+ }
+ }
+ }
+ return
+}
+
+// DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
+type DomainRealm map[string]string
+
+// Parse the lines of the [domain_realm] section of the configuration and add to the mapping.
+func (d *DomainRealm) parseLines(lines []string) error {
+ for _, line := range lines {
+ //Remove comments after the values
+ if idx := strings.IndexAny(line, "#;"); idx != -1 {
+ line = line[:idx]
+ }
+ if strings.TrimSpace(line) == "" {
+ continue
+ }
+ if !strings.Contains(line, "=") {
+ return InvalidErrorf("realm line (%s)", line)
+ }
+ p := strings.Split(line, "=")
+ domain := strings.TrimSpace(strings.ToLower(p[0]))
+ realm := strings.TrimSpace(p[1])
+ d.addMapping(domain, realm)
+ }
+ return nil
+}
+
+// Add a domain to realm mapping.
+func (d *DomainRealm) addMapping(domain, realm string) {
+ (*d)[domain] = realm
+}
+
+// Delete a domain to realm mapping.
+func (d *DomainRealm) deleteMapping(domain, realm string) {
+ delete(*d, domain)
+}
+
+// ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping.
+// The most specific mapping is returned.
+func (c *Config) ResolveRealm(domainName string) string {
+ domainName = strings.TrimSuffix(domainName, ".")
+
+ // Try to match the entire hostname first
+ if r, ok := c.DomainRealm[domainName]; ok {
+ return r
+ }
+
+ // Try to match all DNS domain parts
+ periods := strings.Count(domainName, ".") + 1
+ for i := 2; i <= periods; i++ {
+ z := strings.SplitN(domainName, ".", i)
+ if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok {
+ return r
+ }
+ }
+ return c.LibDefaults.DefaultRealm
+}
+
+// Load the KRB5 configuration from the specified file path.
+func Load(cfgPath string) (*Config, error) {
+ fh, err := os.Open(cfgPath)
+ if err != nil {
+ return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error())
+ }
+ defer fh.Close()
+ scanner := bufio.NewScanner(fh)
+ return NewConfigFromScanner(scanner)
+}
+
+// NewConfigFromString creates a new Config struct from a string.
+func NewConfigFromString(s string) (*Config, error) {
+ reader := strings.NewReader(s)
+ return NewConfigFromReader(reader)
+}
+
+// NewConfigFromReader creates a new Config struct from an io.Reader.
+func NewConfigFromReader(r io.Reader) (*Config, error) {
+ scanner := bufio.NewScanner(r)
+ return NewConfigFromScanner(scanner)
+}
+
+// NewConfigFromScanner creates a new Config struct from a bufio.Scanner.
+func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) {
+ c := NewConfig()
+ var e error
+ sections := make(map[int]string)
+ var sectionLineNum []int
+ var lines []string
+ for scanner.Scan() {
+ // Skip comments and blank lines
+ if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched {
+ continue
+ }
+ if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched {
+ sections[len(lines)] = "libdefaults"
+ sectionLineNum = append(sectionLineNum, len(lines))
+ continue
+ }
+ if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched {
+ sections[len(lines)] = "realms"
+ sectionLineNum = append(sectionLineNum, len(lines))
+ continue
+ }
+ if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched {
+ sections[len(lines)] = "domain_realm"
+ sectionLineNum = append(sectionLineNum, len(lines))
+ continue
+ }
+ if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched {
+ sections[len(lines)] = "unknown_section"
+ sectionLineNum = append(sectionLineNum, len(lines))
+ continue
+ }
+ lines = append(lines, scanner.Text())
+ }
+ for i, start := range sectionLineNum {
+ var end int
+ if i+1 >= len(sectionLineNum) {
+ end = len(lines)
+ } else {
+ end = sectionLineNum[i+1]
+ }
+ switch section := sections[start]; section {
+ case "libdefaults":
+ err := c.LibDefaults.parseLines(lines[start:end])
+ if err != nil {
+ if _, ok := err.(UnsupportedDirective); !ok {
+ return nil, fmt.Errorf("error processing libdefaults section: %v", err)
+ }
+ e = err
+ }
+ case "realms":
+ realms, err := parseRealms(lines[start:end])
+ if err != nil {
+ if _, ok := err.(UnsupportedDirective); !ok {
+ return nil, fmt.Errorf("error processing realms section: %v", err)
+ }
+ e = err
+ }
+ c.Realms = realms
+ case "domain_realm":
+ err := c.DomainRealm.parseLines(lines[start:end])
+ if err != nil {
+ if _, ok := err.(UnsupportedDirective); !ok {
+ return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
+ }
+ e = err
+ }
+ default:
+ continue
+ }
+ }
+ return c, e
+}
+
+// Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.
+func parseETypes(s []string, w bool) []int32 {
+ var eti []int32
+ for _, et := range s {
+ if !w {
+ var weak bool
+ for _, wet := range strings.Fields(WeakETypeList) {
+ if et == wet {
+ weak = true
+ break
+ }
+ }
+ if weak {
+ continue
+ }
+ }
+ i := etypeID.EtypeSupported(et)
+ if i != 0 {
+ eti = append(eti, i)
+ }
+ }
+ return eti
+}
+
+// Parse a time duration string in the configuration to a golang time.Duration.
+func parseDuration(s string) (time.Duration, error) {
+ s = strings.Replace(strings.TrimSpace(s), " ", "", -1)
+
+ // handle Nd[NmNs]
+ if strings.Contains(s, "d") {
+ ds := strings.SplitN(s, "d", 2)
+ dn, err := strconv.ParseUint(ds[0], 10, 32)
+ if err != nil {
+ return time.Duration(0), errors.New("invalid time duration")
+ }
+ d := time.Duration(dn*24) * time.Hour
+ if ds[1] != "" {
+ dp, err := time.ParseDuration(ds[1])
+ if err != nil {
+ return time.Duration(0), errors.New("invalid time duration")
+ }
+ d = d + dp
+ }
+ return d, nil
+ }
+
+ // handle Nm[Ns]
+ d, err := time.ParseDuration(s)
+ if err == nil {
+ return d, nil
+ }
+
+ // handle N
+ v, err := strconv.ParseUint(s, 10, 32)
+ if err == nil && v > 0 {
+ return time.Duration(v) * time.Second, nil
+ }
+
+ // handle h:m[:s]
+ if strings.Contains(s, ":") {
+ t := strings.Split(s, ":")
+ if 2 > len(t) || len(t) > 3 {
+ return time.Duration(0), errors.New("invalid time duration value")
+ }
+ var i []int
+ for _, n := range t {
+ j, err := strconv.ParseInt(n, 10, 16)
+ if err != nil {
+ return time.Duration(0), errors.New("invalid time duration value")
+ }
+ i = append(i, int(j))
+ }
+ d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute
+ if len(i) == 3 {
+ d = d + time.Duration(i[2])*time.Second
+ }
+ return d, nil
+ }
+ return time.Duration(0), errors.New("invalid time duration value")
+}
+
+// Parse possible boolean values to golang bool.
+func parseBoolean(s string) (bool, error) {
+ s = strings.TrimSpace(s)
+ v, err := strconv.ParseBool(s)
+ if err == nil {
+ return v, nil
+ }
+ switch strings.ToLower(s) {
+ case "yes":
+ return true, nil
+ case "y":
+ return true, nil
+ case "no":
+ return false, nil
+ case "n":
+ return false, nil
+ }
+ return false, errors.New("invalid boolean value")
+}
+
+// Parse array of strings but stop if an asterisk is placed at the end of a line.
+func appendUntilFinal(s *[]string, value string, final *bool) {
+ if *final {
+ return
+ }
+ if last := len(value) - 1; last >= 0 && value[last] == '*' {
+ *final = true
+ value = value[:len(value)-1]
+ }
+ *s = append(*s, value)
+}