Repository: jcmturner/gokrb5
Pull request #198 (2018-09-08 15:45:15)

ignore v4 krb5 config lines
relates to #196 


Patch:
diff --git a/config/error.go b/config/error.go
new file mode 100644
index 0000000..5284738
--- /dev/null
+++ b/config/error.go
@@ -0,0 +1,30 @@
+package config
+
+import "fmt"
+
+// Unsupported directive 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/config/krb5conf.go b/config/krb5conf.go
index 7f0fd36..aaf0898 100644
--- a/config/krb5conf.go
+++ b/config/krb5conf.go
@@ -123,11 +123,8 @@ func (l *LibDefaults) parseLines(lines []string) error {
 		if line == "" {
 			continue
 		}
-		if strings.Contains(line, "v4_") {
-			return errors.New("v4 configurations are not supported in Realms section")
-		}
 		if !strings.Contains(line, "=") {
-			return fmt.Errorf("libdefaults configuration line invalid: %s", line)
+			return InvalidErrorf("libdefaults section line (%s)", line)
 		}
 
 		p := strings.Split(line, "=")
@@ -136,26 +133,26 @@ func (l *LibDefaults) parseLines(lines []string) error {
 		case "allow_weak_crypto":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.AllowWeakCrypto = v
 		case "canonicalize":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				return InvalidErrorf("libdefaults section line (%s)", line)
 			}
 			l.CCacheType = int(v)
 		case "clockskew":
 			d, err := parseDuration(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.Clockskew = d
 		case "default_client_keytab_name":
@@ -171,19 +168,19 @@ func (l *LibDefaults) parseLines(lines []string) error {
 		case "dns_canonicalize_hostname":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.DNSLookupRealm = v
 		case "extra_addresses":
@@ -196,19 +193,19 @@ func (l *LibDefaults) parseLines(lines []string) error {
 		case "forwardable":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.IgnoreAcceptorHostname = v
 		case "k5login_authoritative":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.K5LoginAuthoritative = v
 		case "k5login_directory":
@@ -218,7 +215,7 @@ func (l *LibDefaults) parseLines(lines []string) error {
 			v = strings.Replace(v, "0x", "", -1)
 			b, err := hex.DecodeString(v)
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.KDCDefaultOptions.Bytes = b
 			l.KDCDefaultOptions.BitLength = len(b) * 8
@@ -226,13 +223,13 @@ func (l *LibDefaults) parseLines(lines []string) error {
 			p[1] = strings.TrimSpace(p[1])
 			v, err := strconv.ParseInt(p[1], 10, 32)
 			if err != nil || v < 0 {
-				return fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				return InvalidErrorf("libdefaults section line (%s)", line)
 			}
 			l.KDCTimeSync = int(v)
 		case "noaddresses":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.NoAddresses = v
 		case "permitted_enctypes":
@@ -244,7 +241,7 @@ func (l *LibDefaults) parseLines(lines []string) error {
 			for _, s := range t {
 				i, err := strconv.ParseInt(s, 10, 32)
 				if err != nil {
-					return fmt.Errorf("libdefaults configuration line invalid: %s", line)
+					return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 				}
 				v = append(v, int(i))
 			}
@@ -252,52 +249,52 @@ func (l *LibDefaults) parseLines(lines []string) error {
 		case "proxiable":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.Proxiable = v
 		case "rdns":
 			v, err := parseBoolean(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				return InvalidErrorf("libdefaults section line (%s)", line)
 			}
 			l.RealmTryDomains = int(v)
 		case "renew_lifetime":
 			d, err := parseDuration(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				return InvalidErrorf("libdefaults section line (%s)", line)
 			}
 			l.SafeChecksumType = int(v)
 		case "ticket_lifetime":
 			d, err := parseDuration(p[1])
 			if err != nil {
-				return fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				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 fmt.Errorf("libdefaults configuration line invalid: %s", line)
+				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 fmt.Errorf("libdefaults configuration line invalid. %v: %s", err, line)
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
 			}
 			l.VerifyAPReqNofail = v
 		default:
@@ -324,18 +321,47 @@ type Realm struct {
 }
 
 // Parse the lines of a [realms] entry into the Realm struct.
-func (r *Realm) parseLines(name string, lines []string) error {
+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 strings.TrimSpace(line) == "" {
+		if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
 			continue
 		}
-		if !strings.Contains(line, "=") {
-			return fmt.Errorf("realm configuration line invalid: %s", line)
+		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, "=")
@@ -372,45 +398,54 @@ func (r *Realm) parseLines(name string, lines []string) error {
 			r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
 		}
 	}
-	return nil
+	return
 }
 
 // Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
-func parseRealms(lines []string) ([]Realm, error) {
-	var realms []Realm
-	start := -1
+func parseRealms(lines []string) (realms []Realm, err error) {
 	var name string
+	var start int
+	var c int
 	for i, l := range lines {
-		if strings.TrimSpace(l) == "" {
+		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, "v4_") {
+		//	return nil, errors.New("v4 configurations are not supported in Realms section")
+		//}
 		if strings.Contains(l, "{") {
-			if start >= 0 {
-				// already started a block!!!
-				return nil, errors.New("invalid Realms section in configuration")
-			}
-			start = i
+			c++
 			if !strings.Contains(l, "=") {
 				return nil, fmt.Errorf("realm configuration line invalid: %s", l)
 			}
-			p := strings.Split(l, "=")
-			name = strings.TrimSpace(p[0])
+			if c == 1 {
+				start = i
+				p := strings.Split(l, "=")
+				name = strings.TrimSpace(p[0])
+			}
 		}
 		if strings.Contains(l, "}") {
-			if start < 0 {
+			if c < 1 {
 				// but not started a block!!!
 				return nil, errors.New("invalid Realms section in configuration")
 			}
-			var r Realm
-			r.parseLines(name, lines[start+1:i])
-			realms = append(realms, r)
-			start = -1
+			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 realms, nil
+	return
 }
 
 // DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
@@ -423,7 +458,7 @@ func (d *DomainRealm) parseLines(lines []string) error {
 			continue
 		}
 		if !strings.Contains(line, "=") {
-			return fmt.Errorf("realm configuration line invalid: %s", line)
+			return InvalidErrorf("realm line (%s)", line)
 		}
 		p := strings.Split(line, "=")
 		domain := strings.TrimSpace(strings.ToLower(p[0]))
@@ -490,6 +525,7 @@ func NewConfigFromReader(r io.Reader) (*Config, error) {
 // 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
@@ -531,24 +567,33 @@ func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) {
 		case "libdefaults":
 			err := c.LibDefaults.parseLines(lines[start:end])
 			if err != nil {
-				return nil, fmt.Errorf("error processing libdefaults section: %v", err)
+				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 {
-				return nil, fmt.Errorf("error processing realms section: %v", err)
+				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 {
-				return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
+				if _, ok := err.(UnsupportedDirective); !ok {
+					return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
+				}
+				e = err
 			}
 		default:
 			continue
 		}
 	}
-	return c, nil
+	return c, e
 }
 
 // Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.


Test patch:
diff --git a/config/krb5conf_test.go b/config/krb5conf_test.go
index 8b5a57f..6675bfc 100644
--- a/config/krb5conf_test.go
+++ b/config/krb5conf_test.go
@@ -231,6 +231,76 @@ const (
 	forwardable = true
 	krb4_convert = false
 }`
+
+	krb5ConfV4Lines = `
+[logging]
+ default = FILE:/var/log/kerberos/krb5libs.log
+ kdc = FILE:/var/log/kerberos/krb5kdc.log
+ admin_server = FILE:/var/log/kerberos/kadmind.log
+
+[libdefaults]
+ default_realm = TEST.GOKRB5
+ dns_lookup_realm = false
+
+ dns_lookup_kdc = false
+ #dns_lookup_kdc = true
+ ;dns_lookup_kdc = true
+#dns_lookup_kdc = true
+;dns_lookup_kdc = true
+ ticket_lifetime = 10h
+ forwardable = yes
+ default_keytab_name = FILE:/etc/krb5.keytab
+
+ default_client_keytab_name = FILE:/home/gokrb5/client.keytab
+ default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
+
+
+[realms]
+ TEST.GOKRB5 = {
+  kdc = 10.80.88.88:88
+  kdc = assume.port.num
+  kdc = some.other.port:1234
+
+  kdc = 10.80.88.88*
+  kdc = 10.1.2.3.4:88
+
+  admin_server = 10.80.88.88:749
+  default_domain = test.gokrb5
+    v4_name_convert = {
+     host = {
+        rcmd = host
+     }
+   }
+ }
+ EXAMPLE.COM = {
+        kdc = kerberos.example.com
+        kdc = kerberos-1.example.com
+        admin_server = kerberos.example.com
+        auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*//
+ }
+
+
+[domain_realm]
+ .test.gokrb5 = TEST.GOKRB5
+
+ test.gokrb5 = TEST.GOKRB5
+ 
+  .example.com = EXAMPLE.COM
+ hostname1.example.com = EXAMPLE.COM
+ hostname2.example.com = TEST.GOKRB5
+
+
+[appdefaults]
+ pam = {
+   debug = false
+
+   ticket_lifetime = 36000
+
+   renew_lifetime = 36000
+   forwardable = true
+   krb4_convert = false
+ }
+`
 )
 
 func TestLoad(t *testing.T) {
@@ -267,6 +337,43 @@ func TestLoad(t *testing.T) {
 
 }
 
+func TestLoadWithV4Lines(t *testing.T) {
+	t.Parallel()
+	cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5.conf")
+	defer os.Remove(cf.Name())
+	cf.WriteString(krb5ConfV4Lines)
+
+	c, err := Load(cf.Name())
+	if err == nil {
+		t.Fatalf("error should not be nil for config that includes v4 lines")
+	}
+	if _, ok := err.(UnsupportedDirective); !ok {
+		t.Fatalf("error should be of type UnsupportedDirective: %v", err)
+	}
+
+	assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected")
+	assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected")
+	assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected")
+	assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected")
+	assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected")
+	assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected")
+	assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected")
+	assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected")
+
+	assert.Equal(t, 2, len(c.Realms), "Number of realms not as expected")
+	assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd")
+	assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd")
+	assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd")
+	assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd")
+	assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd")
+	assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd")
+	assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd")
+
+	assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected")
+	assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected")
+
+}
+
 func TestLoad2(t *testing.T) {
 	t.Parallel()
 	c, err := NewConfigFromString(krb5Conf2)