github.com/jcmturner/gokrb5/v8@v8.4.4/config/krb5conf.go (about) 1 // 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 2 package config 3 4 import ( 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. 24 type 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. 34 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" 35 36 // New creates a new config struct instance. 37 func 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. 46 type 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. 87 func 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 98 l := LibDefaults{ 99 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 } 118 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 122 } 123 124 // Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct. 125 func (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. 318 type 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. 330 func (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. 412 func 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. 463 type DomainRealm map[string]string 464 465 // Parse the lines of the [domain_realm] section of the configuration and add to the mapping. 466 func (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. 487 func (d *DomainRealm) addMapping(domain, realm string) { 488 (*d)[domain] = realm 489 } 490 491 // Delete a domain to realm mapping. 492 func (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. 498 func (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 } 514 return "" 515 } 516 517 // Load the KRB5 configuration from the specified file path. 518 func 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. 529 func 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. 535 func 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. 541 func 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. 613 func 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. 637 func 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. 694 func 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. 714 func 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. 726 func (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 }