listgroups_unix.mx raw

   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