...
Run Format

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

View as plain text