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