github.com/gidoBOSSftw5731/go/src@v0.0.0-20210226122457-d24b0edbf019/os/user/lookup_unix.go (about)

     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  func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) {
    37  	bs := bufio.NewScanner(r)
    38  	for bs.Scan() {
    39  		line := bs.Bytes()
    40  		// There's no spec for /etc/passwd or /etc/group, but we try to follow
    41  		// the same rules as the glibc parser, which allows comments and blank
    42  		// space at the beginning of a line.
    43  		line = bytes.TrimSpace(line)
    44  		if len(line) == 0 || line[0] == '#' {
    45  			continue
    46  		}
    47  		v, err = fn(line)
    48  		if v != nil || err != nil {
    49  			return
    50  		}
    51  	}
    52  	return nil, bs.Err()
    53  }
    54  
    55  func matchGroupIndexValue(value string, idx int) lineFunc {
    56  	var leadColon string
    57  	if idx > 0 {
    58  		leadColon = ":"
    59  	}
    60  	substr := []byte(leadColon + value + ":")
    61  	return func(line []byte) (v interface{}, err error) {
    62  		if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 {
    63  			return
    64  		}
    65  		// wheel:*:0:root
    66  		parts := strings.SplitN(string(line), ":", 4)
    67  		if len(parts) < 4 || parts[0] == "" || parts[idx] != value ||
    68  			// If the file contains +foo and you search for "foo", glibc
    69  			// returns an "invalid argument" error. Similarly, if you search
    70  			// for a gid for a row where the group name starts with "+" or "-",
    71  			// glibc fails to find the record.
    72  			parts[0][0] == '+' || parts[0][0] == '-' {
    73  			return
    74  		}
    75  		if _, err := strconv.Atoi(parts[2]); err != nil {
    76  			return nil, nil
    77  		}
    78  		return &Group{Name: parts[0], Gid: parts[2]}, nil
    79  	}
    80  }
    81  
    82  func findGroupId(id string, r io.Reader) (*Group, error) {
    83  	if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil {
    84  		return nil, err
    85  	} else if v != nil {
    86  		return v.(*Group), nil
    87  	}
    88  	return nil, UnknownGroupIdError(id)
    89  }
    90  
    91  func findGroupName(name string, r io.Reader) (*Group, error) {
    92  	if v, err := readColonFile(r, matchGroupIndexValue(name, 0)); err != nil {
    93  		return nil, err
    94  	} else if v != nil {
    95  		return v.(*Group), nil
    96  	}
    97  	return nil, UnknownGroupError(name)
    98  }
    99  
   100  // returns a *User for a row if that row's has the given value at the
   101  // given index.
   102  func matchUserIndexValue(value string, idx int) lineFunc {
   103  	var leadColon string
   104  	if idx > 0 {
   105  		leadColon = ":"
   106  	}
   107  	substr := []byte(leadColon + value + ":")
   108  	return func(line []byte) (v interface{}, err error) {
   109  		if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 {
   110  			return
   111  		}
   112  		// kevin:x:1005:1006::/home/kevin:/usr/bin/zsh
   113  		parts := strings.SplitN(string(line), ":", 7)
   114  		if len(parts) < 6 || parts[idx] != value || parts[0] == "" ||
   115  			parts[0][0] == '+' || parts[0][0] == '-' {
   116  			return
   117  		}
   118  		if _, err := strconv.Atoi(parts[2]); err != nil {
   119  			return nil, nil
   120  		}
   121  		if _, err := strconv.Atoi(parts[3]); err != nil {
   122  			return nil, nil
   123  		}
   124  		u := &User{
   125  			Username: parts[0],
   126  			Uid:      parts[2],
   127  			Gid:      parts[3],
   128  			Name:     parts[4],
   129  			HomeDir:  parts[5],
   130  		}
   131  		// The pw_gecos field isn't quite standardized. Some docs
   132  		// say: "It is expected to be a comma separated list of
   133  		// personal data where the first item is the full name of the
   134  		// user."
   135  		if i := strings.Index(u.Name, ","); i >= 0 {
   136  			u.Name = u.Name[:i]
   137  		}
   138  		return u, nil
   139  	}
   140  }
   141  
   142  func findUserId(uid string, r io.Reader) (*User, error) {
   143  	i, e := strconv.Atoi(uid)
   144  	if e != nil {
   145  		return nil, errors.New("user: invalid userid " + uid)
   146  	}
   147  	if v, err := readColonFile(r, matchUserIndexValue(uid, 2)); err != nil {
   148  		return nil, err
   149  	} else if v != nil {
   150  		return v.(*User), nil
   151  	}
   152  	return nil, UnknownUserIdError(i)
   153  }
   154  
   155  func findUsername(name string, r io.Reader) (*User, error) {
   156  	if v, err := readColonFile(r, matchUserIndexValue(name, 0)); err != nil {
   157  		return nil, err
   158  	} else if v != nil {
   159  		return v.(*User), nil
   160  	}
   161  	return nil, UnknownUserError(name)
   162  }
   163  
   164  func lookupGroup(groupname string) (*Group, error) {
   165  	f, err := os.Open(groupFile)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	defer f.Close()
   170  	return findGroupName(groupname, f)
   171  }
   172  
   173  func lookupGroupId(id string) (*Group, error) {
   174  	f, err := os.Open(groupFile)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	defer f.Close()
   179  	return findGroupId(id, f)
   180  }
   181  
   182  func lookupUser(username string) (*User, error) {
   183  	f, err := os.Open(userFile)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	defer f.Close()
   188  	return findUsername(username, f)
   189  }
   190  
   191  func lookupUserId(uid string) (*User, error) {
   192  	f, err := os.Open(userFile)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	defer f.Close()
   197  	return findUserId(uid, f)
   198  }