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 7package net 8 9import ( 10 "errors" 11 "io" 12 "os" 13) 14 15// nssConf represents the state of the machine's /etc/nsswitch.conf file. 16type 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 21type 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. 28func (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. 39type 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. 48func (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 68func 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 77func 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" 136func 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