| // 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 |
| package config |
| |
| import ( |
| "bufio" |
| "encoding/hex" |
| "errors" |
| "fmt" |
| "io" |
| "net" |
| "os" |
| "os/user" |
| "regexp" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/jcmturner/gofork/encoding/asn1" |
| "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID" |
| ) |
| |
| // Config represents the KRB5 configuration. |
| type Config struct { |
| LibDefaults LibDefaults |
| Realms []Realm |
| DomainRealm DomainRealm |
| //CaPaths |
| //AppDefaults |
| //Plugins |
| } |
| |
| // WeakETypeList is a list of encryption types that have been deemed weak. |
| 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" |
| |
| // NewConfig creates a new config struct instance. |
| func NewConfig() *Config { |
| d := make(DomainRealm) |
| return &Config{ |
| LibDefaults: newLibDefaults(), |
| DomainRealm: d, |
| } |
| } |
| |
| // LibDefaults represents the [libdefaults] section of the configuration. |
| type LibDefaults struct { |
| AllowWeakCrypto bool //default false |
| // ap_req_checksum_type int //unlikely to support this |
| Canonicalize bool //default false |
| CCacheType int //default is 4. unlikely to implement older |
| Clockskew time.Duration //max allowed skew in seconds, default 300 |
| //Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory |
| DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab |
| DefaultKeytabName string //default /etc/krb5.keytab |
| DefaultRealm string |
| 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 |
| 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 |
| 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 |
| 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 |
| DNSCanonicalizeHostname bool //default true |
| DNSLookupKDC bool //default false |
| DNSLookupRealm bool |
| ExtraAddresses []net.IP //Not implementing yet |
| Forwardable bool //default false |
| IgnoreAcceptorHostname bool //default false |
| K5LoginAuthoritative bool //default false |
| K5LoginDirectory string //default user's home directory. Must be owned by the user or root |
| KDCDefaultOptions asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK) |
| KDCTimeSync int //default 1 |
| //kdc_req_checksum_type int //unlikely to implement as for very old KDCs |
| NoAddresses bool //default true |
| 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 |
| PermittedEnctypeIDs []int32 |
| //plugin_base_dir string //not supporting plugins |
| PreferredPreauthTypes []int //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported |
| Proxiable bool //default false |
| RDNS bool //default true |
| RealmTryDomains int //default -1 |
| RenewLifetime time.Duration //default 0 |
| SafeChecksumType int //default 8 |
| TicketLifetime time.Duration //default 1 day |
| UDPPreferenceLimit int // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700. |
| VerifyAPReqNofail bool //default false |
| } |
| |
| // Create a new LibDefaults struct. |
| func newLibDefaults() LibDefaults { |
| uid := "0" |
| var hdir string |
| usr, _ := user.Current() |
| if usr != nil { |
| uid = usr.Uid |
| hdir = usr.HomeDir |
| } |
| opts := asn1.BitString{} |
| opts.Bytes, _ = hex.DecodeString("00000010") |
| opts.BitLength = len(opts.Bytes) * 8 |
| return LibDefaults{ |
| CCacheType: 4, |
| Clockskew: time.Duration(300) * time.Second, |
| DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid), |
| DefaultKeytabName: "/etc/krb5.keytab", |
| 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"}, |
| 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"}, |
| DNSCanonicalizeHostname: true, |
| K5LoginDirectory: hdir, |
| KDCDefaultOptions: opts, |
| KDCTimeSync: 1, |
| NoAddresses: true, |
| 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"}, |
| RDNS: true, |
| RealmTryDomains: -1, |
| SafeChecksumType: 8, |
| TicketLifetime: time.Duration(24) * time.Hour, |
| UDPPreferenceLimit: 1465, |
| PreferredPreauthTypes: []int{17, 16, 15, 14}, |
| } |
| } |
| |
| // Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct. |
| func (l *LibDefaults) parseLines(lines []string) error { |
| for _, line := range lines { |
| line = strings.TrimSpace(line) |
| if line == "" { |
| continue |
| } |
| if !strings.Contains(line, "=") { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| |
| p := strings.Split(line, "=") |
| key := strings.TrimSpace(strings.ToLower(p[0])) |
| switch key { |
| case "allow_weak_crypto": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.AllowWeakCrypto = v |
| case "canonicalize": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.Canonicalize = v |
| case "ccache_type": |
| p[1] = strings.TrimSpace(p[1]) |
| v, err := strconv.ParseUint(p[1], 10, 32) |
| if err != nil || v < 0 || v > 4 { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| l.CCacheType = int(v) |
| case "clockskew": |
| d, err := parseDuration(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.Clockskew = d |
| case "default_client_keytab_name": |
| l.DefaultClientKeytabName = strings.TrimSpace(p[1]) |
| case "default_keytab_name": |
| l.DefaultKeytabName = strings.TrimSpace(p[1]) |
| case "default_realm": |
| l.DefaultRealm = strings.TrimSpace(p[1]) |
| case "default_tgs_enctypes": |
| l.DefaultTGSEnctypes = strings.Fields(p[1]) |
| case "default_tkt_enctypes": |
| l.DefaultTktEnctypes = strings.Fields(p[1]) |
| case "dns_canonicalize_hostname": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.DNSCanonicalizeHostname = v |
| case "dns_lookup_kdc": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.DNSLookupKDC = v |
| case "dns_lookup_realm": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.DNSLookupRealm = v |
| case "extra_addresses": |
| ipStr := strings.TrimSpace(p[1]) |
| for _, ip := range strings.Split(ipStr, ",") { |
| if eip := net.ParseIP(ip); eip != nil { |
| l.ExtraAddresses = append(l.ExtraAddresses, eip) |
| } |
| } |
| case "forwardable": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.Forwardable = v |
| case "ignore_acceptor_hostname": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.IgnoreAcceptorHostname = v |
| case "k5login_authoritative": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.K5LoginAuthoritative = v |
| case "k5login_directory": |
| l.K5LoginDirectory = strings.TrimSpace(p[1]) |
| case "kdc_default_options": |
| v := strings.TrimSpace(p[1]) |
| v = strings.Replace(v, "0x", "", -1) |
| b, err := hex.DecodeString(v) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.KDCDefaultOptions.Bytes = b |
| l.KDCDefaultOptions.BitLength = len(b) * 8 |
| case "kdc_timesync": |
| p[1] = strings.TrimSpace(p[1]) |
| v, err := strconv.ParseInt(p[1], 10, 32) |
| if err != nil || v < 0 { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| l.KDCTimeSync = int(v) |
| case "noaddresses": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.NoAddresses = v |
| case "permitted_enctypes": |
| l.PermittedEnctypes = strings.Fields(p[1]) |
| case "preferred_preauth_types": |
| p[1] = strings.TrimSpace(p[1]) |
| t := strings.Split(p[1], ",") |
| var v []int |
| for _, s := range t { |
| i, err := strconv.ParseInt(s, 10, 32) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| v = append(v, int(i)) |
| } |
| l.PreferredPreauthTypes = v |
| case "proxiable": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.Proxiable = v |
| case "rdns": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.RDNS = v |
| case "realm_try_domains": |
| p[1] = strings.TrimSpace(p[1]) |
| v, err := strconv.ParseInt(p[1], 10, 32) |
| if err != nil || v < -1 { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| l.RealmTryDomains = int(v) |
| case "renew_lifetime": |
| d, err := parseDuration(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.RenewLifetime = d |
| case "safe_checksum_type": |
| p[1] = strings.TrimSpace(p[1]) |
| v, err := strconv.ParseInt(p[1], 10, 32) |
| if err != nil || v < 0 { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| l.SafeChecksumType = int(v) |
| case "ticket_lifetime": |
| d, err := parseDuration(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.TicketLifetime = d |
| case "udp_preference_limit": |
| p[1] = strings.TrimSpace(p[1]) |
| v, err := strconv.ParseUint(p[1], 10, 32) |
| if err != nil || v > 32700 { |
| return InvalidErrorf("libdefaults section line (%s)", line) |
| } |
| l.UDPPreferenceLimit = int(v) |
| case "verify_ap_req_nofail": |
| v, err := parseBoolean(p[1]) |
| if err != nil { |
| return InvalidErrorf("libdefaults section line (%s): %v", line, err) |
| } |
| l.VerifyAPReqNofail = v |
| default: |
| //Ignore the line |
| continue |
| } |
| } |
| l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto) |
| l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto) |
| l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto) |
| return nil |
| } |
| |
| // Realm represents an entry in the [realms] section of the configuration. |
| type Realm struct { |
| Realm string |
| AdminServer []string |
| //auth_to_local //Not implementing for now |
| //auth_to_local_names //Not implementing for now |
| DefaultDomain string |
| KDC []string |
| KPasswdServer []string //default admin_server:464 |
| MasterKDC []string |
| } |
| |
| // Parse the lines of a [realms] entry into the Realm struct. |
| func (r *Realm) parseLines(name string, lines []string) (err error) { |
| r.Realm = name |
| var adminServerFinal bool |
| var KDCFinal bool |
| var kpasswdServerFinal bool |
| var masterKDCFinal bool |
| var ignore bool |
| var c int // counts the depth of blocks within brackets { } |
| for _, line := range lines { |
| if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") { |
| continue |
| } |
| line = strings.TrimSpace(line) |
| if line == "" { |
| continue |
| } |
| if !strings.Contains(line, "=") && !strings.Contains(line, "}") { |
| return InvalidErrorf("realms section line (%s)", line) |
| } |
| if strings.Contains(line, "v4_") { |
| ignore = true |
| err = UnsupportedDirective{"v4 configurations are not supported"} |
| } |
| if strings.Contains(line, "{") { |
| c++ |
| if ignore { |
| continue |
| } |
| } |
| if strings.Contains(line, "}") { |
| c-- |
| if c < 0 { |
| return InvalidErrorf("unpaired curly brackets") |
| } |
| if ignore { |
| if c < 1 { |
| c = 0 |
| ignore = false |
| } |
| continue |
| } |
| } |
| |
| p := strings.Split(line, "=") |
| key := strings.TrimSpace(strings.ToLower(p[0])) |
| v := strings.TrimSpace(p[1]) |
| switch key { |
| case "admin_server": |
| appendUntilFinal(&r.AdminServer, v, &adminServerFinal) |
| case "default_domain": |
| r.DefaultDomain = v |
| case "kdc": |
| if !strings.Contains(v, ":") { |
| // No port number specified default to 88 |
| if strings.HasSuffix(v, `*`) { |
| v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*" |
| } else { |
| v = strings.TrimSpace(v) + ":88" |
| } |
| } |
| appendUntilFinal(&r.KDC, v, &KDCFinal) |
| case "kpasswd_server": |
| appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal) |
| case "master_kdc": |
| appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal) |
| default: |
| //Ignore the line |
| continue |
| } |
| } |
| //default for Kpasswd_server = admin_server:464 |
| if len(r.KPasswdServer) < 1 { |
| for _, a := range r.AdminServer { |
| s := strings.Split(a, ":") |
| r.KPasswdServer = append(r.KPasswdServer, s[0]+":464") |
| } |
| } |
| return |
| } |
| |
| // Parse the lines of the [realms] section of the configuration into an slice of Realm structs. |
| func parseRealms(lines []string) (realms []Realm, err error) { |
| var name string |
| var start int |
| var c int |
| for i, l := range lines { |
| l = strings.TrimSpace(l) |
| if l == "" { |
| continue |
| } |
| //if strings.Contains(l, "v4_") { |
| // return nil, errors.New("v4 configurations are not supported in Realms section") |
| //} |
| if strings.Contains(l, "{") { |
| c++ |
| if !strings.Contains(l, "=") { |
| return nil, fmt.Errorf("realm configuration line invalid: %s", l) |
| } |
| if c == 1 { |
| start = i |
| p := strings.Split(l, "=") |
| name = strings.TrimSpace(p[0]) |
| } |
| } |
| if strings.Contains(l, "}") { |
| if c < 1 { |
| // but not started a block!!! |
| return nil, errors.New("invalid Realms section in configuration") |
| } |
| c-- |
| if c == 0 { |
| var r Realm |
| e := r.parseLines(name, lines[start+1:i]) |
| if e != nil { |
| if _, ok := e.(UnsupportedDirective); !ok { |
| err = e |
| return |
| } |
| err = e |
| } |
| realms = append(realms, r) |
| } |
| } |
| } |
| return |
| } |
| |
| // DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration. |
| type DomainRealm map[string]string |
| |
| // Parse the lines of the [domain_realm] section of the configuration and add to the mapping. |
| func (d *DomainRealm) parseLines(lines []string) error { |
| for _, line := range lines { |
| if strings.TrimSpace(line) == "" { |
| continue |
| } |
| if !strings.Contains(line, "=") { |
| return InvalidErrorf("realm line (%s)", line) |
| } |
| p := strings.Split(line, "=") |
| domain := strings.TrimSpace(strings.ToLower(p[0])) |
| realm := strings.TrimSpace(p[1]) |
| d.addMapping(domain, realm) |
| } |
| return nil |
| } |
| |
| // Add a domain to realm mapping. |
| func (d *DomainRealm) addMapping(domain, realm string) { |
| (*d)[domain] = realm |
| } |
| |
| // Delete a domain to realm mapping. |
| func (d *DomainRealm) deleteMapping(domain, realm string) { |
| delete(*d, domain) |
| } |
| |
| // ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping. |
| // The most specific mapping is returned. |
| func (c *Config) ResolveRealm(domainName string) string { |
| domainName = strings.TrimSuffix(domainName, ".") |
| |
| // Try to match the entire hostname first |
| if r, ok := c.DomainRealm[domainName]; ok { |
| return r |
| } |
| |
| // Try to match all DNS domain parts |
| periods := strings.Count(domainName, ".") + 1 |
| for i := 2; i <= periods; i++ { |
| z := strings.SplitN(domainName, ".", i) |
| if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok { |
| return r |
| } |
| } |
| return c.LibDefaults.DefaultRealm |
| } |
| |
| // Load the KRB5 configuration from the specified file path. |
| func Load(cfgPath string) (*Config, error) { |
| fh, err := os.Open(cfgPath) |
| if err != nil { |
| return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error()) |
| } |
| defer fh.Close() |
| scanner := bufio.NewScanner(fh) |
| return NewConfigFromScanner(scanner) |
| } |
| |
| // NewConfigFromString creates a new Config struct from a string. |
| func NewConfigFromString(s string) (*Config, error) { |
| reader := strings.NewReader(s) |
| return NewConfigFromReader(reader) |
| } |
| |
| // NewConfigFromReader creates a new Config struct from an io.Reader. |
| func NewConfigFromReader(r io.Reader) (*Config, error) { |
| scanner := bufio.NewScanner(r) |
| return NewConfigFromScanner(scanner) |
| } |
| |
| // NewConfigFromScanner creates a new Config struct from a bufio.Scanner. |
| func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) { |
| c := NewConfig() |
| var e error |
| sections := make(map[int]string) |
| var sectionLineNum []int |
| var lines []string |
| for scanner.Scan() { |
| // Skip comments and blank lines |
| if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched { |
| continue |
| } |
| if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched { |
| sections[len(lines)] = "libdefaults" |
| sectionLineNum = append(sectionLineNum, len(lines)) |
| continue |
| } |
| if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched { |
| sections[len(lines)] = "realms" |
| sectionLineNum = append(sectionLineNum, len(lines)) |
| continue |
| } |
| if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched { |
| sections[len(lines)] = "domain_realm" |
| sectionLineNum = append(sectionLineNum, len(lines)) |
| continue |
| } |
| if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched { |
| sections[len(lines)] = "unknown_section" |
| sectionLineNum = append(sectionLineNum, len(lines)) |
| continue |
| } |
| lines = append(lines, scanner.Text()) |
| } |
| for i, start := range sectionLineNum { |
| var end int |
| if i+1 >= len(sectionLineNum) { |
| end = len(lines) |
| } else { |
| end = sectionLineNum[i+1] |
| } |
| switch section := sections[start]; section { |
| case "libdefaults": |
| err := c.LibDefaults.parseLines(lines[start:end]) |
| if err != nil { |
| if _, ok := err.(UnsupportedDirective); !ok { |
| return nil, fmt.Errorf("error processing libdefaults section: %v", err) |
| } |
| e = err |
| } |
| case "realms": |
| realms, err := parseRealms(lines[start:end]) |
| if err != nil { |
| if _, ok := err.(UnsupportedDirective); !ok { |
| return nil, fmt.Errorf("error processing realms section: %v", err) |
| } |
| e = err |
| } |
| c.Realms = realms |
| case "domain_realm": |
| err := c.DomainRealm.parseLines(lines[start:end]) |
| if err != nil { |
| if _, ok := err.(UnsupportedDirective); !ok { |
| return nil, fmt.Errorf("error processing domaain_realm section: %v", err) |
| } |
| e = err |
| } |
| default: |
| continue |
| } |
| } |
| return c, e |
| } |
| |
| // Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes. |
| func parseETypes(s []string, w bool) []int32 { |
| var eti []int32 |
| for _, et := range s { |
| if !w { |
| var weak bool |
| for _, wet := range strings.Fields(WeakETypeList) { |
| if et == wet { |
| weak = true |
| break |
| } |
| } |
| if weak { |
| continue |
| } |
| } |
| i := etypeID.EtypeSupported(et) |
| if i != 0 { |
| eti = append(eti, i) |
| } |
| } |
| return eti |
| } |
| |
| // Parse a time duration string in the configuration to a golang time.Duration. |
| func parseDuration(s string) (time.Duration, error) { |
| s = strings.Replace(strings.TrimSpace(s), " ", "", -1) |
| |
| // handle Nd[NmNs] |
| if strings.Contains(s, "d") { |
| ds := strings.SplitN(s, "d", 2) |
| dn, err := strconv.ParseUint(ds[0], 10, 32) |
| if err != nil { |
| return time.Duration(0), errors.New("invalid time duration") |
| } |
| d := time.Duration(dn*24) * time.Hour |
| if ds[1] != "" { |
| dp, err := time.ParseDuration(ds[1]) |
| if err != nil { |
| return time.Duration(0), errors.New("invalid time duration") |
| } |
| d = d + dp |
| } |
| return d, nil |
| } |
| |
| // handle Nm[Ns] |
| d, err := time.ParseDuration(s) |
| if err == nil { |
| return d, nil |
| } |
| |
| // handle N |
| v, err := strconv.ParseUint(s, 10, 32) |
| if err == nil && v > 0 { |
| return time.Duration(v) * time.Second, nil |
| } |
| |
| // handle h:m[:s] |
| if strings.Contains(s, ":") { |
| t := strings.Split(s, ":") |
| if 2 > len(t) || len(t) > 3 { |
| return time.Duration(0), errors.New("invalid time duration value") |
| } |
| var i []int |
| for _, n := range t { |
| j, err := strconv.ParseInt(n, 10, 16) |
| if err != nil { |
| return time.Duration(0), errors.New("invalid time duration value") |
| } |
| i = append(i, int(j)) |
| } |
| d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute |
| if len(i) == 3 { |
| d = d + time.Duration(i[2])*time.Second |
| } |
| return d, nil |
| } |
| return time.Duration(0), errors.New("invalid time duration value") |
| } |
| |
| // Parse possible boolean values to golang bool. |
| func parseBoolean(s string) (bool, error) { |
| s = strings.TrimSpace(s) |
| v, err := strconv.ParseBool(s) |
| if err == nil { |
| return v, nil |
| } |
| switch strings.ToLower(s) { |
| case "yes": |
| return true, nil |
| case "y": |
| return true, nil |
| case "no": |
| return false, nil |
| case "n": |
| return false, nil |
| } |
| return false, errors.New("invalid boolean value") |
| } |
| |
| // Parse array of strings but stop if an asterisk is placed at the end of a line. |
| func appendUntilFinal(s *[]string, value string, final *bool) { |
| if *final { |
| return |
| } |
| if last := len(value) - 1; last >= 0 && value[last] == '*' { |
| *final = true |
| value = value[:len(value)-1] |
| } |
| *s = append(*s, value) |
| } |