blob: f448c8a4f5efdaff253b48b8c0c22d8d329c86f5 [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001// 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
2package config
3
4import (
5 "bufio"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "io"
11 "net"
12 "os"
13 "os/user"
14 "regexp"
15 "strconv"
16 "strings"
17 "time"
18
19 "github.com/jcmturner/gofork/encoding/asn1"
20 "github.com/jcmturner/gokrb5/v8/iana/etypeID"
21)
22
23// Config represents the KRB5 configuration.
24type Config struct {
25 LibDefaults LibDefaults
26 Realms []Realm
27 DomainRealm DomainRealm
28 //CaPaths
29 //AppDefaults
30 //Plugins
31}
32
33// WeakETypeList is a list of encryption types that have been deemed weak.
34const 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"
35
36// New creates a new config struct instance.
37func New() *Config {
38 d := make(DomainRealm)
39 return &Config{
40 LibDefaults: newLibDefaults(),
41 DomainRealm: d,
42 }
43}
44
45// LibDefaults represents the [libdefaults] section of the configuration.
46type LibDefaults struct {
47 AllowWeakCrypto bool //default false
48 // ap_req_checksum_type int //unlikely to support this
49 Canonicalize bool //default false
50 CCacheType int //default is 4. unlikely to implement older
51 Clockskew time.Duration //max allowed skew in seconds, default 300
52 //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory
53 DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab
54 DefaultKeytabName string //default /etc/krb5.keytab
55 DefaultRealm string
56 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
57 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
58 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
59 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
60 DNSCanonicalizeHostname bool //default true
61 DNSLookupKDC bool //default false
62 DNSLookupRealm bool
63 ExtraAddresses []net.IP //Not implementing yet
64 Forwardable bool //default false
65 IgnoreAcceptorHostname bool //default false
66 K5LoginAuthoritative bool //default false
67 K5LoginDirectory string //default user's home directory. Must be owned by the user or root
68 KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK)
69 KDCTimeSync int //default 1
70 //kdc_req_checksum_type int //unlikely to implement as for very old KDCs
71 NoAddresses bool //default true
72 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
73 PermittedEnctypeIDs []int32
74 //plugin_base_dir string //not supporting plugins
75 PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported
76 Proxiable bool //default false
77 RDNS bool //default true
78 RealmTryDomains int //default -1
79 RenewLifetime time.Duration //default 0
80 SafeChecksumType int //default 8
81 TicketLifetime time.Duration //default 1 day
82 UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700.
83 VerifyAPReqNofail bool //default false
84}
85
86// Create a new LibDefaults struct.
87func newLibDefaults() LibDefaults {
88 uid := "0"
89 var hdir string
90 usr, _ := user.Current()
91 if usr != nil {
92 uid = usr.Uid
93 hdir = usr.HomeDir
94 }
95 opts := asn1.BitString{}
96 opts.Bytes, _ = hex.DecodeString("00000010")
97 opts.BitLength = len(opts.Bytes) * 8
Abhay Kumara2ae5992025-11-10 14:02:24 +000098 l := LibDefaults{
khenaidood948f772021-08-11 17:49:24 -040099 CCacheType: 4,
100 Clockskew: time.Duration(300) * time.Second,
101 DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid),
102 DefaultKeytabName: "/etc/krb5.keytab",
103 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"},
104 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"},
105 DNSCanonicalizeHostname: true,
106 K5LoginDirectory: hdir,
107 KDCDefaultOptions: opts,
108 KDCTimeSync: 1,
109 NoAddresses: true,
110 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"},
111 RDNS: true,
112 RealmTryDomains: -1,
113 SafeChecksumType: 8,
114 TicketLifetime: time.Duration(24) * time.Hour,
115 UDPPreferenceLimit: 1465,
116 PreferredPreauthTypes: []int{17, 16, 15, 14},
117 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000118 l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
119 l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
120 l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
121 return l
khenaidood948f772021-08-11 17:49:24 -0400122}
123
124// Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct.
125func (l *LibDefaults) parseLines(lines []string) error {
126 for _, line := range lines {
127 //Remove comments after the values
128 if idx := strings.IndexAny(line, "#;"); idx != -1 {
129 line = line[:idx]
130 }
131 line = strings.TrimSpace(line)
132 if line == "" {
133 continue
134 }
135 if !strings.Contains(line, "=") {
136 return InvalidErrorf("libdefaults section line (%s)", line)
137 }
138
139 p := strings.Split(line, "=")
140 key := strings.TrimSpace(strings.ToLower(p[0]))
141 switch key {
142 case "allow_weak_crypto":
143 v, err := parseBoolean(p[1])
144 if err != nil {
145 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
146 }
147 l.AllowWeakCrypto = v
148 case "canonicalize":
149 v, err := parseBoolean(p[1])
150 if err != nil {
151 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
152 }
153 l.Canonicalize = v
154 case "ccache_type":
155 p[1] = strings.TrimSpace(p[1])
156 v, err := strconv.ParseUint(p[1], 10, 32)
157 if err != nil || v < 0 || v > 4 {
158 return InvalidErrorf("libdefaults section line (%s)", line)
159 }
160 l.CCacheType = int(v)
161 case "clockskew":
162 d, err := parseDuration(p[1])
163 if err != nil {
164 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
165 }
166 l.Clockskew = d
167 case "default_client_keytab_name":
168 l.DefaultClientKeytabName = strings.TrimSpace(p[1])
169 case "default_keytab_name":
170 l.DefaultKeytabName = strings.TrimSpace(p[1])
171 case "default_realm":
172 l.DefaultRealm = strings.TrimSpace(p[1])
173 case "default_tgs_enctypes":
174 l.DefaultTGSEnctypes = strings.Fields(p[1])
175 case "default_tkt_enctypes":
176 l.DefaultTktEnctypes = strings.Fields(p[1])
177 case "dns_canonicalize_hostname":
178 v, err := parseBoolean(p[1])
179 if err != nil {
180 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
181 }
182 l.DNSCanonicalizeHostname = v
183 case "dns_lookup_kdc":
184 v, err := parseBoolean(p[1])
185 if err != nil {
186 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
187 }
188 l.DNSLookupKDC = v
189 case "dns_lookup_realm":
190 v, err := parseBoolean(p[1])
191 if err != nil {
192 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
193 }
194 l.DNSLookupRealm = v
195 case "extra_addresses":
196 ipStr := strings.TrimSpace(p[1])
197 for _, ip := range strings.Split(ipStr, ",") {
198 if eip := net.ParseIP(ip); eip != nil {
199 l.ExtraAddresses = append(l.ExtraAddresses, eip)
200 }
201 }
202 case "forwardable":
203 v, err := parseBoolean(p[1])
204 if err != nil {
205 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
206 }
207 l.Forwardable = v
208 case "ignore_acceptor_hostname":
209 v, err := parseBoolean(p[1])
210 if err != nil {
211 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
212 }
213 l.IgnoreAcceptorHostname = v
214 case "k5login_authoritative":
215 v, err := parseBoolean(p[1])
216 if err != nil {
217 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
218 }
219 l.K5LoginAuthoritative = v
220 case "k5login_directory":
221 l.K5LoginDirectory = strings.TrimSpace(p[1])
222 case "kdc_default_options":
223 v := strings.TrimSpace(p[1])
224 v = strings.Replace(v, "0x", "", -1)
225 b, err := hex.DecodeString(v)
226 if err != nil {
227 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
228 }
229 l.KDCDefaultOptions.Bytes = b
230 l.KDCDefaultOptions.BitLength = len(b) * 8
231 case "kdc_timesync":
232 p[1] = strings.TrimSpace(p[1])
233 v, err := strconv.ParseInt(p[1], 10, 32)
234 if err != nil || v < 0 {
235 return InvalidErrorf("libdefaults section line (%s)", line)
236 }
237 l.KDCTimeSync = int(v)
238 case "noaddresses":
239 v, err := parseBoolean(p[1])
240 if err != nil {
241 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
242 }
243 l.NoAddresses = v
244 case "permitted_enctypes":
245 l.PermittedEnctypes = strings.Fields(p[1])
246 case "preferred_preauth_types":
247 p[1] = strings.TrimSpace(p[1])
248 t := strings.Split(p[1], ",")
249 var v []int
250 for _, s := range t {
251 i, err := strconv.ParseInt(s, 10, 32)
252 if err != nil {
253 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
254 }
255 v = append(v, int(i))
256 }
257 l.PreferredPreauthTypes = v
258 case "proxiable":
259 v, err := parseBoolean(p[1])
260 if err != nil {
261 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
262 }
263 l.Proxiable = v
264 case "rdns":
265 v, err := parseBoolean(p[1])
266 if err != nil {
267 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
268 }
269 l.RDNS = v
270 case "realm_try_domains":
271 p[1] = strings.TrimSpace(p[1])
272 v, err := strconv.ParseInt(p[1], 10, 32)
273 if err != nil || v < -1 {
274 return InvalidErrorf("libdefaults section line (%s)", line)
275 }
276 l.RealmTryDomains = int(v)
277 case "renew_lifetime":
278 d, err := parseDuration(p[1])
279 if err != nil {
280 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
281 }
282 l.RenewLifetime = d
283 case "safe_checksum_type":
284 p[1] = strings.TrimSpace(p[1])
285 v, err := strconv.ParseInt(p[1], 10, 32)
286 if err != nil || v < 0 {
287 return InvalidErrorf("libdefaults section line (%s)", line)
288 }
289 l.SafeChecksumType = int(v)
290 case "ticket_lifetime":
291 d, err := parseDuration(p[1])
292 if err != nil {
293 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
294 }
295 l.TicketLifetime = d
296 case "udp_preference_limit":
297 p[1] = strings.TrimSpace(p[1])
298 v, err := strconv.ParseUint(p[1], 10, 32)
299 if err != nil || v > 32700 {
300 return InvalidErrorf("libdefaults section line (%s)", line)
301 }
302 l.UDPPreferenceLimit = int(v)
303 case "verify_ap_req_nofail":
304 v, err := parseBoolean(p[1])
305 if err != nil {
306 return InvalidErrorf("libdefaults section line (%s): %v", line, err)
307 }
308 l.VerifyAPReqNofail = v
309 }
310 }
311 l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
312 l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
313 l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
314 return nil
315}
316
317// Realm represents an entry in the [realms] section of the configuration.
318type Realm struct {
319 Realm string
320 AdminServer []string
321 //auth_to_local //Not implementing for now
322 //auth_to_local_names //Not implementing for now
323 DefaultDomain string
324 KDC []string
325 KPasswdServer []string //default admin_server:464
326 MasterKDC []string
327}
328
329// Parse the lines of a [realms] entry into the Realm struct.
330func (r *Realm) parseLines(name string, lines []string) (err error) {
331 r.Realm = name
332 var adminServerFinal bool
333 var KDCFinal bool
334 var kpasswdServerFinal bool
335 var masterKDCFinal bool
336 var ignore bool
337 var c int // counts the depth of blocks within brackets { }
338 for _, line := range lines {
339 if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
340 continue
341 }
342 //Remove comments after the values
343 if idx := strings.IndexAny(line, "#;"); idx != -1 {
344 line = line[:idx]
345 }
346 line = strings.TrimSpace(line)
347 if line == "" {
348 continue
349 }
350 if !strings.Contains(line, "=") && !strings.Contains(line, "}") {
351 return InvalidErrorf("realms section line (%s)", line)
352 }
353 if strings.Contains(line, "v4_") {
354 ignore = true
355 err = UnsupportedDirective{"v4 configurations are not supported"}
356 }
357 if strings.Contains(line, "{") {
358 c++
359 if ignore {
360 continue
361 }
362 }
363 if strings.Contains(line, "}") {
364 c--
365 if c < 0 {
366 return InvalidErrorf("unpaired curly brackets")
367 }
368 if ignore {
369 if c < 1 {
370 c = 0
371 ignore = false
372 }
373 continue
374 }
375 }
376
377 p := strings.Split(line, "=")
378 key := strings.TrimSpace(strings.ToLower(p[0]))
379 v := strings.TrimSpace(p[1])
380 switch key {
381 case "admin_server":
382 appendUntilFinal(&r.AdminServer, v, &adminServerFinal)
383 case "default_domain":
384 r.DefaultDomain = v
385 case "kdc":
386 if !strings.Contains(v, ":") {
387 // No port number specified default to 88
388 if strings.HasSuffix(v, `*`) {
389 v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*"
390 } else {
391 v = strings.TrimSpace(v) + ":88"
392 }
393 }
394 appendUntilFinal(&r.KDC, v, &KDCFinal)
395 case "kpasswd_server":
396 appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal)
397 case "master_kdc":
398 appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal)
399 }
400 }
401 //default for Kpasswd_server = admin_server:464
402 if len(r.KPasswdServer) < 1 {
403 for _, a := range r.AdminServer {
404 s := strings.Split(a, ":")
405 r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
406 }
407 }
408 return
409}
410
411// Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
412func parseRealms(lines []string) (realms []Realm, err error) {
413 var name string
414 var start int
415 var c int
416 for i, l := range lines {
417 //Remove comments after the values
418 if idx := strings.IndexAny(l, "#;"); idx != -1 {
419 l = l[:idx]
420 }
421 l = strings.TrimSpace(l)
422 if l == "" {
423 continue
424 }
425 //if strings.Contains(l, "v4_") {
426 // return nil, errors.New("v4 configurations are not supported in Realms section")
427 //}
428 if strings.Contains(l, "{") {
429 c++
430 if !strings.Contains(l, "=") {
431 return nil, fmt.Errorf("realm configuration line invalid: %s", l)
432 }
433 if c == 1 {
434 start = i
435 p := strings.Split(l, "=")
436 name = strings.TrimSpace(p[0])
437 }
438 }
439 if strings.Contains(l, "}") {
440 if c < 1 {
441 // but not started a block!!!
442 return nil, errors.New("invalid Realms section in configuration")
443 }
444 c--
445 if c == 0 {
446 var r Realm
447 e := r.parseLines(name, lines[start+1:i])
448 if e != nil {
449 if _, ok := e.(UnsupportedDirective); !ok {
450 err = e
451 return
452 }
453 err = e
454 }
455 realms = append(realms, r)
456 }
457 }
458 }
459 return
460}
461
462// DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
463type DomainRealm map[string]string
464
465// Parse the lines of the [domain_realm] section of the configuration and add to the mapping.
466func (d *DomainRealm) parseLines(lines []string) error {
467 for _, line := range lines {
468 //Remove comments after the values
469 if idx := strings.IndexAny(line, "#;"); idx != -1 {
470 line = line[:idx]
471 }
472 if strings.TrimSpace(line) == "" {
473 continue
474 }
475 if !strings.Contains(line, "=") {
476 return InvalidErrorf("realm line (%s)", line)
477 }
478 p := strings.Split(line, "=")
479 domain := strings.TrimSpace(strings.ToLower(p[0]))
480 realm := strings.TrimSpace(p[1])
481 d.addMapping(domain, realm)
482 }
483 return nil
484}
485
486// Add a domain to realm mapping.
487func (d *DomainRealm) addMapping(domain, realm string) {
488 (*d)[domain] = realm
489}
490
491// Delete a domain to realm mapping.
492func (d *DomainRealm) deleteMapping(domain, realm string) {
493 delete(*d, domain)
494}
495
496// ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping.
497// The most specific mapping is returned.
498func (c *Config) ResolveRealm(domainName string) string {
499 domainName = strings.TrimSuffix(domainName, ".")
500
501 // Try to match the entire hostname first
502 if r, ok := c.DomainRealm[domainName]; ok {
503 return r
504 }
505
506 // Try to match all DNS domain parts
507 periods := strings.Count(domainName, ".") + 1
508 for i := 2; i <= periods; i++ {
509 z := strings.SplitN(domainName, ".", i)
510 if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok {
511 return r
512 }
513 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000514 return ""
khenaidood948f772021-08-11 17:49:24 -0400515}
516
517// Load the KRB5 configuration from the specified file path.
518func Load(cfgPath string) (*Config, error) {
519 fh, err := os.Open(cfgPath)
520 if err != nil {
521 return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error())
522 }
523 defer fh.Close()
524 scanner := bufio.NewScanner(fh)
525 return NewFromScanner(scanner)
526}
527
528// NewFromString creates a new Config struct from a string.
529func NewFromString(s string) (*Config, error) {
530 reader := strings.NewReader(s)
531 return NewFromReader(reader)
532}
533
534// NewFromReader creates a new Config struct from an io.Reader.
535func NewFromReader(r io.Reader) (*Config, error) {
536 scanner := bufio.NewScanner(r)
537 return NewFromScanner(scanner)
538}
539
540// NewFromScanner creates a new Config struct from a bufio.Scanner.
541func NewFromScanner(scanner *bufio.Scanner) (*Config, error) {
542 c := New()
543 var e error
544 sections := make(map[int]string)
545 var sectionLineNum []int
546 var lines []string
547 for scanner.Scan() {
548 // Skip comments and blank lines
549 if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched {
550 continue
551 }
552 if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched {
553 sections[len(lines)] = "libdefaults"
554 sectionLineNum = append(sectionLineNum, len(lines))
555 continue
556 }
557 if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched {
558 sections[len(lines)] = "realms"
559 sectionLineNum = append(sectionLineNum, len(lines))
560 continue
561 }
562 if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched {
563 sections[len(lines)] = "domain_realm"
564 sectionLineNum = append(sectionLineNum, len(lines))
565 continue
566 }
567 if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched {
568 sections[len(lines)] = "unknown_section"
569 sectionLineNum = append(sectionLineNum, len(lines))
570 continue
571 }
572 lines = append(lines, scanner.Text())
573 }
574 for i, start := range sectionLineNum {
575 var end int
576 if i+1 >= len(sectionLineNum) {
577 end = len(lines)
578 } else {
579 end = sectionLineNum[i+1]
580 }
581 switch section := sections[start]; section {
582 case "libdefaults":
583 err := c.LibDefaults.parseLines(lines[start:end])
584 if err != nil {
585 if _, ok := err.(UnsupportedDirective); !ok {
586 return nil, fmt.Errorf("error processing libdefaults section: %v", err)
587 }
588 e = err
589 }
590 case "realms":
591 realms, err := parseRealms(lines[start:end])
592 if err != nil {
593 if _, ok := err.(UnsupportedDirective); !ok {
594 return nil, fmt.Errorf("error processing realms section: %v", err)
595 }
596 e = err
597 }
598 c.Realms = realms
599 case "domain_realm":
600 err := c.DomainRealm.parseLines(lines[start:end])
601 if err != nil {
602 if _, ok := err.(UnsupportedDirective); !ok {
603 return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
604 }
605 e = err
606 }
607 }
608 }
609 return c, e
610}
611
612// Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.
613func parseETypes(s []string, w bool) []int32 {
614 var eti []int32
615 for _, et := range s {
616 if !w {
617 var weak bool
618 for _, wet := range strings.Fields(WeakETypeList) {
619 if et == wet {
620 weak = true
621 break
622 }
623 }
624 if weak {
625 continue
626 }
627 }
628 i := etypeID.EtypeSupported(et)
629 if i != 0 {
630 eti = append(eti, i)
631 }
632 }
633 return eti
634}
635
636// Parse a time duration string in the configuration to a golang time.Duration.
637func parseDuration(s string) (time.Duration, error) {
638 s = strings.Replace(strings.TrimSpace(s), " ", "", -1)
639
640 // handle Nd[NmNs]
641 if strings.Contains(s, "d") {
642 ds := strings.SplitN(s, "d", 2)
643 dn, err := strconv.ParseUint(ds[0], 10, 32)
644 if err != nil {
645 return time.Duration(0), errors.New("invalid time duration")
646 }
647 d := time.Duration(dn*24) * time.Hour
648 if ds[1] != "" {
649 dp, err := time.ParseDuration(ds[1])
650 if err != nil {
651 return time.Duration(0), errors.New("invalid time duration")
652 }
653 d = d + dp
654 }
655 return d, nil
656 }
657
658 // handle Nm[Ns]
659 d, err := time.ParseDuration(s)
660 if err == nil {
661 return d, nil
662 }
663
664 // handle N
665 v, err := strconv.ParseUint(s, 10, 32)
666 if err == nil && v > 0 {
667 return time.Duration(v) * time.Second, nil
668 }
669
670 // handle h:m[:s]
671 if strings.Contains(s, ":") {
672 t := strings.Split(s, ":")
673 if 2 > len(t) || len(t) > 3 {
674 return time.Duration(0), errors.New("invalid time duration value")
675 }
676 var i []int
677 for _, n := range t {
678 j, err := strconv.ParseInt(n, 10, 16)
679 if err != nil {
680 return time.Duration(0), errors.New("invalid time duration value")
681 }
682 i = append(i, int(j))
683 }
684 d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute
685 if len(i) == 3 {
686 d = d + time.Duration(i[2])*time.Second
687 }
688 return d, nil
689 }
690 return time.Duration(0), errors.New("invalid time duration value")
691}
692
693// Parse possible boolean values to golang bool.
694func parseBoolean(s string) (bool, error) {
695 s = strings.TrimSpace(s)
696 v, err := strconv.ParseBool(s)
697 if err == nil {
698 return v, nil
699 }
700 switch strings.ToLower(s) {
701 case "yes":
702 return true, nil
703 case "y":
704 return true, nil
705 case "no":
706 return false, nil
707 case "n":
708 return false, nil
709 }
710 return false, errors.New("invalid boolean value")
711}
712
713// Parse array of strings but stop if an asterisk is placed at the end of a line.
714func appendUntilFinal(s *[]string, value string, final *bool) {
715 if *final {
716 return
717 }
718 if last := len(value) - 1; last >= 0 && value[last] == '*' {
719 *final = true
720 value = value[:len(value)-1]
721 }
722 *s = append(*s, value)
723}
724
725// JSON return details of the config in a JSON format.
726func (c *Config) JSON() (string, error) {
727 b, err := json.MarshalIndent(c, "", " ")
728 if err != nil {
729 return "", err
730 }
731 return string(b), nil
732}