github.com/lovishpuri/go-40569/src@v0.0.0-20230519171745-f8623e7c56cf/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 ((unix && !android) || (js && wasm) || wasip1) && ((!cgo && !darwin) || osusergo) 6 7 package user 8 9 import ( 10 "bufio" 11 "bytes" 12 "errors" 13 "io" 14 "os" 15 "strconv" 16 "strings" 17 ) 18 19 // lineFunc returns a value, an error, or (nil, nil) to skip the row. 20 type lineFunc func(line []byte) (v any, err error) 21 22 // readColonFile parses r as an /etc/group or /etc/passwd style file, running 23 // fn for each row. readColonFile returns a value, an error, or (nil, nil) if 24 // the end of the file is reached without a match. 25 // 26 // readCols is the minimum number of colon-separated fields that will be passed 27 // to fn; in a long line additional fields may be silently discarded. 28 func readColonFile(r io.Reader, fn lineFunc, readCols int) (v any, err error) { 29 rd := bufio.NewReader(r) 30 31 // Read the file line-by-line. 32 for { 33 var isPrefix bool 34 var wholeLine []byte 35 36 // Read the next line. We do so in chunks (as much as reader's 37 // buffer is able to keep), check if we read enough columns 38 // already on each step and store final result in wholeLine. 39 for { 40 var line []byte 41 line, isPrefix, err = rd.ReadLine() 42 43 if err != nil { 44 // We should return (nil, nil) if EOF is reached 45 // without a match. 46 if err == io.EOF { 47 err = nil 48 } 49 return nil, err 50 } 51 52 // Simple common case: line is short enough to fit in a 53 // single reader's buffer. 54 if !isPrefix && len(wholeLine) == 0 { 55 wholeLine = line 56 break 57 } 58 59 wholeLine = append(wholeLine, line...) 60 61 // Check if we read the whole line (or enough columns) 62 // already. 63 if !isPrefix || bytes.Count(wholeLine, []byte{':'}) >= readCols { 64 break 65 } 66 } 67 68 // There's no spec for /etc/passwd or /etc/group, but we try to follow 69 // the same rules as the glibc parser, which allows comments and blank 70 // space at the beginning of a line. 71 wholeLine = bytes.TrimSpace(wholeLine) 72 if len(wholeLine) == 0 || wholeLine[0] == '#' { 73 continue 74 } 75 v, err = fn(wholeLine) 76 if v != nil || err != nil { 77 return 78 } 79 80 // If necessary, skip the rest of the line 81 for ; isPrefix; _, isPrefix, err = rd.ReadLine() { 82 if err != nil { 83 // We should return (nil, nil) if EOF is reached without a match. 84 if err == io.EOF { 85 err = nil 86 } 87 return nil, err 88 } 89 } 90 } 91 } 92 93 func matchGroupIndexValue(value string, idx int) lineFunc { 94 var leadColon string 95 if idx > 0 { 96 leadColon = ":" 97 } 98 substr := []byte(leadColon + value + ":") 99 return func(line []byte) (v any, err error) { 100 if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 { 101 return 102 } 103 // wheel:*:0:root 104 parts := strings.SplitN(string(line), ":", 4) 105 if len(parts) < 4 || parts[0] == "" || parts[idx] != value || 106 // If the file contains +foo and you search for "foo", glibc 107 // returns an "invalid argument" error. Similarly, if you search 108 // for a gid for a row where the group name starts with "+" or "-", 109 // glibc fails to find the record. 110 parts[0][0] == '+' || parts[0][0] == '-' { 111 return 112 } 113 if _, err := strconv.Atoi(parts[2]); err != nil { 114 return nil, nil 115 } 116 return &Group{Name: parts[0], Gid: parts[2]}, nil 117 } 118 } 119 120 func findGroupId(id string, r io.Reader) (*Group, error) { 121 if v, err := readColonFile(r, matchGroupIndexValue(id, 2), 3); err != nil { 122 return nil, err 123 } else if v != nil { 124 return v.(*Group), nil 125 } 126 return nil, UnknownGroupIdError(id) 127 } 128 129 func findGroupName(name string, r io.Reader) (*Group, error) { 130 if v, err := readColonFile(r, matchGroupIndexValue(name, 0), 3); err != nil { 131 return nil, err 132 } else if v != nil { 133 return v.(*Group), nil 134 } 135 return nil, UnknownGroupError(name) 136 } 137 138 // returns a *User for a row if that row's has the given value at the 139 // given index. 140 func matchUserIndexValue(value string, idx int) lineFunc { 141 var leadColon string 142 if idx > 0 { 143 leadColon = ":" 144 } 145 substr := []byte(leadColon + value + ":") 146 return func(line []byte) (v any, err error) { 147 if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 { 148 return 149 } 150 // kevin:x:1005:1006::/home/kevin:/usr/bin/zsh 151 parts := strings.SplitN(string(line), ":", 7) 152 if len(parts) < 6 || parts[idx] != value || parts[0] == "" || 153 parts[0][0] == '+' || parts[0][0] == '-' { 154 return 155 } 156 if _, err := strconv.Atoi(parts[2]); err != nil { 157 return nil, nil 158 } 159 if _, err := strconv.Atoi(parts[3]); err != nil { 160 return nil, nil 161 } 162 u := &User{ 163 Username: parts[0], 164 Uid: parts[2], 165 Gid: parts[3], 166 Name: parts[4], 167 HomeDir: parts[5], 168 } 169 // The pw_gecos field isn't quite standardized. Some docs 170 // say: "It is expected to be a comma separated list of 171 // personal data where the first item is the full name of the 172 // user." 173 u.Name, _, _ = strings.Cut(u.Name, ",") 174 return u, nil 175 } 176 } 177 178 func findUserId(uid string, r io.Reader) (*User, error) { 179 i, e := strconv.Atoi(uid) 180 if e != nil { 181 return nil, errors.New("user: invalid userid " + uid) 182 } 183 if v, err := readColonFile(r, matchUserIndexValue(uid, 2), 6); err != nil { 184 return nil, err 185 } else if v != nil { 186 return v.(*User), nil 187 } 188 return nil, UnknownUserIdError(i) 189 } 190 191 func findUsername(name string, r io.Reader) (*User, error) { 192 if v, err := readColonFile(r, matchUserIndexValue(name, 0), 6); err != nil { 193 return nil, err 194 } else if v != nil { 195 return v.(*User), nil 196 } 197 return nil, UnknownUserError(name) 198 } 199 200 func lookupGroup(groupname string) (*Group, error) { 201 f, err := os.Open(groupFile) 202 if err != nil { 203 return nil, err 204 } 205 defer f.Close() 206 return findGroupName(groupname, f) 207 } 208 209 func lookupGroupId(id string) (*Group, error) { 210 f, err := os.Open(groupFile) 211 if err != nil { 212 return nil, err 213 } 214 defer f.Close() 215 return findGroupId(id, f) 216 } 217 218 func lookupUser(username string) (*User, error) { 219 f, err := os.Open(userFile) 220 if err != nil { 221 return nil, err 222 } 223 defer f.Close() 224 return findUsername(username, f) 225 } 226 227 func lookupUserId(uid string) (*User, error) { 228 f, err := os.Open(userFile) 229 if err != nil { 230 return nil, err 231 } 232 defer f.Close() 233 return findUserId(uid, f) 234 }