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