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