Source file src/net/nss.go

     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  package net
     6  
     7  import (
     8  	"errors"
     9  	"internal/bytealg"
    10  	"os"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  const (
    16  	nssConfigPath = "/etc/nsswitch.conf"
    17  )
    18  
    19  var nssConfig nsswitchConfig
    20  
    21  type 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  
    33  func 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.
    42  func (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.
    49  func (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  
    78  func (conf *nsswitchConfig) acquireSema() {
    79  	conf.ch <- struct{}{}
    80  }
    81  
    82  func (conf *nsswitchConfig) tryAcquireSema() bool {
    83  	select {
    84  	case conf.ch <- struct{}{}:
    85  		return true
    86  	default:
    87  		return false
    88  	}
    89  }
    90  
    91  func (conf *nsswitchConfig) releaseSema() {
    92  	<-conf.ch
    93  }
    94  
    95  // nssConf represents the state of the machine's /etc/nsswitch.conf file.
    96  type 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  
   102  type 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.
   109  func (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.
   120  type 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.
   129  func (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  
   149  func 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  
   165  func 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"
   222  func 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  

View as plain text