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