1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package net 8 9 import ( 10 "errors" 11 "io" 12 "os" 13 ) 14 15 // nssConf represents the state of the machine's /etc/nsswitch.conf file. 16 type nssConf struct { 17 err error // any error encountered opening or parsing the file 18 sources map[string][]nssSource // keyed by database (e.g. "hosts") 19 } 20 21 type nssSource struct { 22 source string // e.g. "compat", "files", "mdns4_minimal" 23 criteria []nssCriterion 24 } 25 26 // standardCriteria reports all specified criteria have the default 27 // status actions. 28 func (s nssSource) standardCriteria() bool { 29 for i, crit := range s.criteria { 30 if !crit.standardStatusAction(i == len(s.criteria)-1) { 31 return false 32 } 33 } 34 return true 35 } 36 37 // nssCriterion is the parsed structure of one of the criteria in brackets 38 // after an NSS source name. 39 type nssCriterion struct { 40 negate bool // if "!" was present 41 status string // e.g. "success", "unavail" (lowercase) 42 action string // e.g. "return", "continue" (lowercase) 43 } 44 45 // standardStatusAction reports whether c is equivalent to not 46 // specifying the criterion at all. last is whether this criteria is the 47 // last in the list. 48 func (c nssCriterion) standardStatusAction(last bool) bool { 49 if c.negate { 50 return false 51 } 52 var def string 53 switch c.status { 54 case "success": 55 def = "return" 56 case "notfound", "unavail", "tryagain": 57 def = "continue" 58 default: 59 // Unknown status 60 return false 61 } 62 if last && c.action == "return" { 63 return true 64 } 65 return c.action == def 66 } 67 68 func parseNSSConfFile(file string) *nssConf { 69 f, err := os.Open(file) 70 if err != nil { 71 return &nssConf{err: err} 72 } 73 defer f.Close() 74 return parseNSSConf(f) 75 } 76 77 func parseNSSConf(r io.Reader) *nssConf { 78 slurp, err := readFull(r) 79 if err != nil { 80 return &nssConf{err: err} 81 } 82 conf := new(nssConf) 83 conf.err = foreachLine(slurp, func(line []byte) error { 84 line = trimSpace(removeComment(line)) 85 if len(line) == 0 { 86 return nil 87 } 88 colon := bytesIndexByte(line, ':') 89 if colon == -1 { 90 return errors.New("no colon on line") 91 } 92 db := string(trimSpace(line[:colon])) 93 srcs := line[colon+1:] 94 for { 95 srcs = trimSpace(srcs) 96 if len(srcs) == 0 { 97 break 98 } 99 sp := bytesIndexByte(srcs, ' ') 100 var src string 101 if sp == -1 { 102 src = string(srcs) 103 srcs = nil // done 104 } else { 105 src = string(srcs[:sp]) 106 srcs = trimSpace(srcs[sp+1:]) 107 } 108 var criteria []nssCriterion 109 // See if there's a criteria block in brackets. 110 if len(srcs) > 0 && srcs[0] == '[' { 111 bclose := bytesIndexByte(srcs, ']') 112 if bclose == -1 { 113 return errors.New("unclosed criterion bracket") 114 } 115 var err error 116 criteria, err = parseCriteria(srcs[1:bclose]) 117 if err != nil { 118 return errors.New("invalid criteria: " + string(srcs[1:bclose])) 119 } 120 srcs = srcs[bclose+1:] 121 } 122 if conf.sources == nil { 123 conf.sources = make(map[string][]nssSource) 124 } 125 conf.sources[db] = append(conf.sources[db], nssSource{ 126 source: src, 127 criteria: criteria, 128 }) 129 } 130 return nil 131 }) 132 return conf 133 } 134 135 // parses "foo=bar !foo=bar" 136 func parseCriteria(x []byte) (c []nssCriterion, err error) { 137 err = foreachField(x, func(f []byte) error { 138 not := false 139 if len(f) > 0 && f[0] == '!' { 140 not = true 141 f = f[1:] 142 } 143 if len(f) < 3 { 144 return errors.New("criterion too short") 145 } 146 eq := bytesIndexByte(f, '=') 147 if eq == -1 { 148 return errors.New("criterion lacks equal sign") 149 } 150 lowerASCIIBytes(f) 151 c = append(c, nssCriterion{ 152 negate: not, 153 status: string(f[:eq]), 154 action: string(f[eq+1:]), 155 }) 156 return nil 157 }) 158 return 159 } 160