Source file src/os/user/listgroups_unix.go

     1  // Copyright 2021 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 ((darwin || dragonfly || freebsd || (js && wasm) || wasip1 || (!android && linux) || netbsd || openbsd || solaris) && ((!cgo && !darwin) || osusergo)) || aix || illumos
     6  
     7  package user
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"os"
    16  	"strconv"
    17  )
    18  
    19  func listGroupsFromReader(u *User, r io.Reader) ([]string, error) {
    20  	if u.Username == "" {
    21  		return nil, errors.New("user: list groups: empty username")
    22  	}
    23  	primaryGid, err := strconv.Atoi(u.Gid)
    24  	if err != nil {
    25  		return nil, fmt.Errorf("user: list groups for %s: invalid gid %q", u.Username, u.Gid)
    26  	}
    27  
    28  	userCommas := []byte("," + u.Username + ",")  // ,john,
    29  	userFirst := userCommas[1:]                   // john,
    30  	userLast := userCommas[:len(userCommas)-1]    // ,john
    31  	userOnly := userCommas[1 : len(userCommas)-1] // john
    32  
    33  	// Add primary Gid first.
    34  	groups := []string{u.Gid}
    35  
    36  	rd := bufio.NewReader(r)
    37  	done := false
    38  	for !done {
    39  		line, err := rd.ReadBytes('\n')
    40  		if err != nil {
    41  			if err == io.EOF {
    42  				done = true
    43  			} else {
    44  				return groups, err
    45  			}
    46  		}
    47  
    48  		// Look for username in the list of users. If user is found,
    49  		// append the GID to the groups slice.
    50  
    51  		// There's no spec for /etc/passwd or /etc/group, but we try to follow
    52  		// the same rules as the glibc parser, which allows comments and blank
    53  		// space at the beginning of a line.
    54  		line = bytes.TrimSpace(line)
    55  		if len(line) == 0 || line[0] == '#' ||
    56  			// If you search for a gid in a row where the group
    57  			// name (the first field) starts with "+" or "-",
    58  			// glibc fails to find the record, and so should we.
    59  			line[0] == '+' || line[0] == '-' {
    60  			continue
    61  		}
    62  
    63  		// Format of /etc/group is
    64  		// 	groupname:password:GID:user_list
    65  		// for example
    66  		// 	wheel:x:10:john,paul,jack
    67  		//	tcpdump:x:72:
    68  		listIdx := bytes.LastIndexByte(line, ':')
    69  		if listIdx == -1 || listIdx == len(line)-1 {
    70  			// No commas, or empty group list.
    71  			continue
    72  		}
    73  		if bytes.Count(line[:listIdx], colon) != 2 {
    74  			// Incorrect number of colons.
    75  			continue
    76  		}
    77  		list := line[listIdx+1:]
    78  		// Check the list for user without splitting or copying.
    79  		if !(bytes.Equal(list, userOnly) || bytes.HasPrefix(list, userFirst) || bytes.HasSuffix(list, userLast) || bytes.Contains(list, userCommas)) {
    80  			continue
    81  		}
    82  
    83  		// groupname:password:GID
    84  		parts := bytes.Split(line[:listIdx], colon)
    85  		if len(parts) != 3 || len(parts[0]) == 0 {
    86  			continue
    87  		}
    88  		gid := string(parts[2])
    89  		// Make sure it's numeric and not the same as primary GID.
    90  		numGid, err := strconv.Atoi(gid)
    91  		if err != nil || numGid == primaryGid {
    92  			continue
    93  		}
    94  
    95  		groups = append(groups, gid)
    96  	}
    97  
    98  	return groups, nil
    99  }
   100  
   101  func listGroups(u *User) ([]string, error) {
   102  	f, err := os.Open(groupFile)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	defer f.Close()
   107  
   108  	return listGroupsFromReader(u, f)
   109  }
   110  

View as plain text