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