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 5package net 6 7import ( 8 "errors" 9 "internal/bytealg" 10 "os" 11 "sync" 12 "time" 13) 14 15const ( 16 nssConfigPath = "/etc/nsswitch.conf" 17) 18 19var nssConfig nsswitchConfig 20 21type nsswitchConfig struct { 22 initOnce sync.Once // guards init of nsswitchConfig 23 24 // ch is used as a semaphore that only allows one lookup at a 25 // time to recheck nsswitch.conf 26 ch chan struct{} // guards lastChecked and modTime 27 lastChecked time.Time // last time nsswitch.conf was checked 28 29 mu sync.Mutex // protects nssConf 30 nssConf *nssConf 31} 32 33func getSystemNSS() *nssConf { 34 nssConfig.tryUpdate() 35 nssConfig.mu.Lock() 36 conf := nssConfig.nssConf 37 nssConfig.mu.Unlock() 38 return conf 39} 40 41// init initializes conf and is only called via conf.initOnce. 42func (conf *nsswitchConfig) init() { 43 conf.nssConf = parseNSSConfFile("/etc/nsswitch.conf") 44 conf.lastChecked = time.Now() 45 conf.ch = make(chan struct{}, 1) 46} 47 48// tryUpdate tries to update conf. 49func (conf *nsswitchConfig) tryUpdate() { 50 conf.initOnce.Do(conf.init) 51 52 // Ensure only one update at a time checks nsswitch.conf 53 if !conf.tryAcquireSema() { 54 return 55 } 56 defer conf.releaseSema() 57 58 now := time.Now() 59 if conf.lastChecked.After(now.Add(-5 * time.Second)) { 60 return 61 } 62 conf.lastChecked = now 63 64 var mtime time.Time 65 if fi, err := os.Stat(nssConfigPath); err == nil { 66 mtime = fi.ModTime() 67 } 68 if mtime.Equal(conf.nssConf.mtime) { 69 return 70 } 71 72 nssConf := parseNSSConfFile(nssConfigPath) 73 conf.mu.Lock() 74 conf.nssConf = nssConf 75 conf.mu.Unlock() 76} 77 78func (conf *nsswitchConfig) acquireSema() { 79 conf.ch <- struct{}{} 80} 81 82func (conf *nsswitchConfig) tryAcquireSema() bool { 83 select { 84 case conf.ch <- struct{}{}: 85 return true 86 default: 87 return false 88 } 89} 90 91func (conf *nsswitchConfig) releaseSema() { 92 <-conf.ch 93} 94 95// nssConf represents the state of the machine's /etc/nsswitch.conf file. 96type nssConf struct { 97 mtime time.Time // time of nsswitch.conf modification 98 err error // any error encountered opening or parsing the file 99 sources map[string][]nssSource // keyed by database (e.g. "hosts") 100} 101 102type nssSource struct { 103 source string // e.g. "compat", "files", "mdns4_minimal" 104 criteria []nssCriterion 105} 106 107// standardCriteria reports all specified criteria have the default 108// status actions. 109func (s nssSource) standardCriteria() bool { 110 for i, crit := range s.criteria { 111 if !crit.standardStatusAction(i == len(s.criteria)-1) { 112 return false 113 } 114 } 115 return true 116} 117 118// nssCriterion is the parsed structure of one of the criteria in brackets 119// after an NSS source name. 120type nssCriterion struct { 121 negate bool // if "!" was present 122 status string // e.g. "success", "unavail" (lowercase) 123 action string // e.g. "return", "continue" (lowercase) 124} 125 126// standardStatusAction reports whether c is equivalent to not 127// specifying the criterion at all. last is whether this criteria is the 128// last in the list. 129func (c nssCriterion) standardStatusAction(last bool) bool { 130 if c.negate { 131 return false 132 } 133 var def string 134 switch c.status { 135 case "success": 136 def = "return" 137 case "notfound", "unavail", "tryagain": 138 def = "continue" 139 default: 140 // Unknown status 141 return false 142 } 143 if last && c.action == "return" { 144 return true 145 } 146 return c.action == def 147} 148 149func parseNSSConfFile(file string) *nssConf { 150 f, err := open(file) 151 if err != nil { 152 return &nssConf{err: err} 153 } 154 defer f.close() 155 mtime, _, err := f.stat() 156 if err != nil { 157 return &nssConf{err: err} 158 } 159 160 conf := parseNSSConf(f) 161 conf.mtime = mtime 162 return conf 163} 164 165func parseNSSConf(f *file) *nssConf { 166 conf := new(nssConf) 167 for line, ok := f.readLine(); ok; line, ok = f.readLine() { 168 line = trimSpace(removeComment(line)) 169 if len(line) == 0 { 170 continue 171 } 172 colon := bytealg.IndexByteString(line, ':') 173 if colon == -1 { 174 conf.err = errors.New("no colon on line") 175 return conf 176 } 177 db := trimSpace(line[:colon]) 178 srcs := line[colon+1:] 179 for { 180 srcs = trimSpace(srcs) 181 if len(srcs) == 0 { 182 break 183 } 184 sp := bytealg.IndexByteString(srcs, ' ') 185 var src string 186 if sp == -1 { 187 src = srcs 188 srcs = "" // done 189 } else { 190 src = srcs[:sp] 191 srcs = trimSpace(srcs[sp+1:]) 192 } 193 var criteria []nssCriterion 194 // See if there's a criteria block in brackets. 195 if len(srcs) > 0 && srcs[0] == '[' { 196 bclose := bytealg.IndexByteString(srcs, ']') 197 if bclose == -1 { 198 conf.err = errors.New("unclosed criterion bracket") 199 return conf 200 } 201 var err error 202 criteria, err = parseCriteria(srcs[1:bclose]) 203 if err != nil { 204 conf.err = errors.New("invalid criteria: " + srcs[1:bclose]) 205 return conf 206 } 207 srcs = srcs[bclose+1:] 208 } 209 if conf.sources == nil { 210 conf.sources = make(map[string][]nssSource) 211 } 212 conf.sources[db] = append(conf.sources[db], nssSource{ 213 source: src, 214 criteria: criteria, 215 }) 216 } 217 } 218 return conf 219} 220 221// parses "foo=bar !foo=bar" 222func parseCriteria(x string) (c []nssCriterion, err error) { 223 err = foreachField(x, func(f string) error { 224 not := false 225 if len(f) > 0 && f[0] == '!' { 226 not = true 227 f = f[1:] 228 } 229 if len(f) < 3 { 230 return errors.New("criterion too short") 231 } 232 eq := bytealg.IndexByteString(f, '=') 233 if eq == -1 { 234 return errors.New("criterion lacks equal sign") 235 } 236 if hasUpperCase(f) { 237 lower := []byte(f) 238 lowerASCIIBytes(lower) 239 f = string(lower) 240 } 241 c = append(c, nssCriterion{ 242 negate: not, 243 status: f[:eq], 244 action: f[eq+1:], 245 }) 246 return nil 247 }) 248 return 249} 250