Black Lives Matter. Support the Equal Justice Initiative.

Source file src/os/user/lookup_unix.go

Documentation: os/user

     1  // Copyright 2016 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  //go:build (aix || darwin || dragonfly || freebsd || (js && wasm) || (!android && linux) || netbsd || openbsd || solaris) && (!cgo || osusergo)
     6  // +build aix darwin dragonfly freebsd js,wasm !android,linux netbsd openbsd solaris
     7  // +build !cgo osusergo
     8  
     9  package user
    10  
    11  import (
    12  	"bufio"
    13  	"bytes"
    14  	"errors"
    15  	"io"
    16  	"os"
    17  	"strconv"
    18  	"strings"
    19  )
    20  
    21  const groupFile = "/etc/group"
    22  const userFile = "/etc/passwd"
    23  
    24  var colon = []byte{':'}
    25  
    26  func init() {
    27  	groupImplemented = false
    28  }
    29  
    30  // lineFunc returns a value, an error, or (nil, nil) to skip the row.
    31  type lineFunc func(line []byte) (v interface{}, err error)
    32  
    33  // readColonFile parses r as an /etc/group or /etc/passwd style file, running
    34  // fn for each row. readColonFile returns a value, an error, or (nil, nil) if
    35  // the end of the file is reached without a match.
    36  //
    37  // readCols is the minimum number of colon-separated fields that will be passed
    38  // to fn; in a long line additional fields may be silently discarded.
    39  func readColonFile(r io.Reader, fn lineFunc, readCols int) (v interface{}, err error) {
    40  	rd := bufio.NewReader(r)
    41  
    42  	// Read the file line-by-line.
    43  	for {
    44  		var isPrefix bool
    45  		var wholeLine []byte
    46  
    47  		// Read the next line. We do so in chunks (as much as reader's
    48  		// buffer is able to keep), check if we read enough columns
    49  		// already on each step and store final result in wholeLine.
    50  		for {
    51  			var line []byte
    52  			line, isPrefix, err = rd.ReadLine()
    53  
    54  			if err != nil {
    55  				// We should return (nil, nil) if EOF is reached
    56  				// without a match.
    57  				if err == io.EOF {
    58  					err = nil
    59  				}
    60  				return nil, err
    61  			}
    62  
    63  			// Simple common case: line is short enough to fit in a
    64  			// single reader's buffer.
    65  			if !isPrefix && len(wholeLine) == 0 {
    66  				wholeLine = line
    67  				break
    68  			}
    69  
    70  			wholeLine = append(wholeLine, line...)
    71  
    72  			// Check if we read the whole line (or enough columns)
    73  			// already.
    74  			if !isPrefix || bytes.Count(wholeLine, []byte{':'}) >= readCols {
    75  				break
    76  			}
    77  		}
    78  
    79  		// There's no spec for /etc/passwd or /etc/group, but we try to follow
    80  		// the same rules as the glibc parser, which allows comments and blank
    81  		// space at the beginning of a line.
    82  		wholeLine = bytes.TrimSpace(wholeLine)
    83  		if len(wholeLine) == 0 || wholeLine[0] == '#' {
    84  			continue
    85  		}
    86  		v, err = fn(wholeLine)
    87  		if v != nil || err != nil {
    88  			return
    89  		}
    90  
    91  		// If necessary, skip the rest of the line
    92  		for ; isPrefix; _, isPrefix, err = rd.ReadLine() {
    93  			if err != nil {
    94  				// We should return (nil, nil) if EOF is reached without a match.
    95  				if err == io.EOF {
    96  					err = nil
    97  				}
    98  				return nil, err
    99  			}
   100  		}
   101  	}
   102  }
   103  
   104  func matchGroupIndexValue(value string, idx int) lineFunc {
   105  	var leadColon string
   106  	if idx > 0 {
   107  		leadColon = ":"
   108  	}
   109  	substr := []byte(leadColon + value + ":")
   110  	return func(line []byte) (v interface{}, err error) {
   111  		if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 {
   112  			return
   113  		}
   114  		// wheel:*:0:root
   115  		parts := strings.SplitN(string(line), ":", 4)
   116  		if len(parts) < 4 || parts[0] == "" || parts[idx] != value ||
   117  			// If the file contains +foo and you search for "foo", glibc
   118  			// returns an "invalid argument" error. Similarly, if you search
   119  			// for a gid for a row where the group name starts with "+" or "-",
   120  			// glibc fails to find the record.
   121  			parts[0][0] == '+' || parts[0][0] == '-' {
   122  			return
   123  		}
   124  		if _, err := strconv.Atoi(parts[2]); err != nil {
   125  			return nil, nil
   126  		}
   127  		return &Group{Name: parts[0], Gid: parts[2]}, nil
   128  	}
   129  }
   130  
   131  func findGroupId(id string, r io.Reader) (*Group, error) {
   132  	if v, err := readColonFile(r, matchGroupIndexValue(id, 2), 3); err != nil {
   133  		return nil, err
   134  	} else if v != nil {
   135  		return v.(*Group), nil
   136  	}
   137  	return nil, UnknownGroupIdError(id)
   138  }
   139  
   140  func findGroupName(name string, r io.Reader) (*Group, error) {
   141  	if v, err := readColonFile(r, matchGroupIndexValue(name, 0), 3); err != nil {
   142  		return nil, err
   143  	} else if v != nil {
   144  		return v.(*Group), nil
   145  	}
   146  	return nil, UnknownGroupError(name)
   147  }
   148  
   149  // returns a *User for a row if that row's has the given value at the
   150  // given index.
   151  func matchUserIndexValue(value string, idx int) lineFunc {
   152  	var leadColon string
   153  	if idx > 0 {
   154  		leadColon = ":"
   155  	}
   156  	substr := []byte(leadColon + value + ":")
   157  	return func(line []byte) (v interface{}, err error) {
   158  		if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 {
   159  			return
   160  		}
   161  		// kevin:x:1005:1006::/home/kevin:/usr/bin/zsh
   162  		parts := strings.SplitN(string(line), ":", 7)
   163  		if len(parts) < 6 || parts[idx] != value || parts[0] == "" ||
   164  			parts[0][0] == '+' || parts[0][0] == '-' {
   165  			return
   166  		}
   167  		if _, err := strconv.Atoi(parts[2]); err != nil {
   168  			return nil, nil
   169  		}
   170  		if _, err := strconv.Atoi(parts[3]); err != nil {
   171  			return nil, nil
   172  		}
   173  		u := &User{
   174  			Username: parts[0],
   175  			Uid:      parts[2],
   176  			Gid:      parts[3],
   177  			Name:     parts[4],
   178  			HomeDir:  parts[5],
   179  		}
   180  		// The pw_gecos field isn't quite standardized. Some docs
   181  		// say: "It is expected to be a comma separated list of
   182  		// personal data where the first item is the full name of the
   183  		// user."
   184  		if i := strings.Index(u.Name, ","); i >= 0 {
   185  			u.Name = u.Name[:i]
   186  		}
   187  		return u, nil
   188  	}
   189  }
   190  
   191  func findUserId(uid string, r io.Reader) (*User, error) {
   192  	i, e := strconv.Atoi(uid)
   193  	if e != nil {
   194  		return nil, errors.New("user: invalid userid " + uid)
   195  	}
   196  	if v, err := readColonFile(r, matchUserIndexValue(uid, 2), 6); err != nil {
   197  		return nil, err
   198  	} else if v != nil {
   199  		return v.(*User), nil
   200  	}
   201  	return nil, UnknownUserIdError(i)
   202  }
   203  
   204  func findUsername(name string, r io.Reader) (*User, error) {
   205  	if v, err := readColonFile(r, matchUserIndexValue(name, 0), 6); err != nil {
   206  		return nil, err
   207  	} else if v != nil {
   208  		return v.(*User), nil
   209  	}
   210  	return nil, UnknownUserError(name)
   211  }
   212  
   213  func lookupGroup(groupname string) (*Group, error) {
   214  	f, err := os.Open(groupFile)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	defer f.Close()
   219  	return findGroupName(groupname, f)
   220  }
   221  
   222  func lookupGroupId(id string) (*Group, error) {
   223  	f, err := os.Open(groupFile)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	defer f.Close()
   228  	return findGroupId(id, f)
   229  }
   230  
   231  func lookupUser(username string) (*User, error) {
   232  	f, err := os.Open(userFile)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	defer f.Close()
   237  	return findUsername(username, f)
   238  }
   239  
   240  func lookupUserId(uid string) (*User, error) {
   241  	f, err := os.Open(userFile)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	defer f.Close()
   246  	return findUserId(uid, f)
   247  }
   248  

View as plain text