github.com/gidoBOSSftw5731/go/src@v0.0.0-20210226122457-d24b0edbf019/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 func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) { 37 bs := bufio.NewScanner(r) 38 for bs.Scan() { 39 line := bs.Bytes() 40 // There's no spec for /etc/passwd or /etc/group, but we try to follow 41 // the same rules as the glibc parser, which allows comments and blank 42 // space at the beginning of a line. 43 line = bytes.TrimSpace(line) 44 if len(line) == 0 || line[0] == '#' { 45 continue 46 } 47 v, err = fn(line) 48 if v != nil || err != nil { 49 return 50 } 51 } 52 return nil, bs.Err() 53 } 54 55 func matchGroupIndexValue(value string, idx int) lineFunc { 56 var leadColon string 57 if idx > 0 { 58 leadColon = ":" 59 } 60 substr := []byte(leadColon + value + ":") 61 return func(line []byte) (v interface{}, err error) { 62 if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 { 63 return 64 } 65 // wheel:*:0:root 66 parts := strings.SplitN(string(line), ":", 4) 67 if len(parts) < 4 || parts[0] == "" || parts[idx] != value || 68 // If the file contains +foo and you search for "foo", glibc 69 // returns an "invalid argument" error. Similarly, if you search 70 // for a gid for a row where the group name starts with "+" or "-", 71 // glibc fails to find the record. 72 parts[0][0] == '+' || parts[0][0] == '-' { 73 return 74 } 75 if _, err := strconv.Atoi(parts[2]); err != nil { 76 return nil, nil 77 } 78 return &Group{Name: parts[0], Gid: parts[2]}, nil 79 } 80 } 81 82 func findGroupId(id string, r io.Reader) (*Group, error) { 83 if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil { 84 return nil, err 85 } else if v != nil { 86 return v.(*Group), nil 87 } 88 return nil, UnknownGroupIdError(id) 89 } 90 91 func findGroupName(name string, r io.Reader) (*Group, error) { 92 if v, err := readColonFile(r, matchGroupIndexValue(name, 0)); err != nil { 93 return nil, err 94 } else if v != nil { 95 return v.(*Group), nil 96 } 97 return nil, UnknownGroupError(name) 98 } 99 100 // returns a *User for a row if that row's has the given value at the 101 // given index. 102 func matchUserIndexValue(value string, idx int) lineFunc { 103 var leadColon string 104 if idx > 0 { 105 leadColon = ":" 106 } 107 substr := []byte(leadColon + value + ":") 108 return func(line []byte) (v interface{}, err error) { 109 if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 { 110 return 111 } 112 // kevin:x:1005:1006::/home/kevin:/usr/bin/zsh 113 parts := strings.SplitN(string(line), ":", 7) 114 if len(parts) < 6 || parts[idx] != value || parts[0] == "" || 115 parts[0][0] == '+' || parts[0][0] == '-' { 116 return 117 } 118 if _, err := strconv.Atoi(parts[2]); err != nil { 119 return nil, nil 120 } 121 if _, err := strconv.Atoi(parts[3]); err != nil { 122 return nil, nil 123 } 124 u := &User{ 125 Username: parts[0], 126 Uid: parts[2], 127 Gid: parts[3], 128 Name: parts[4], 129 HomeDir: parts[5], 130 } 131 // The pw_gecos field isn't quite standardized. Some docs 132 // say: "It is expected to be a comma separated list of 133 // personal data where the first item is the full name of the 134 // user." 135 if i := strings.Index(u.Name, ","); i >= 0 { 136 u.Name = u.Name[:i] 137 } 138 return u, nil 139 } 140 } 141 142 func findUserId(uid string, r io.Reader) (*User, error) { 143 i, e := strconv.Atoi(uid) 144 if e != nil { 145 return nil, errors.New("user: invalid userid " + uid) 146 } 147 if v, err := readColonFile(r, matchUserIndexValue(uid, 2)); err != nil { 148 return nil, err 149 } else if v != nil { 150 return v.(*User), nil 151 } 152 return nil, UnknownUserIdError(i) 153 } 154 155 func findUsername(name string, r io.Reader) (*User, error) { 156 if v, err := readColonFile(r, matchUserIndexValue(name, 0)); err != nil { 157 return nil, err 158 } else if v != nil { 159 return v.(*User), nil 160 } 161 return nil, UnknownUserError(name) 162 } 163 164 func lookupGroup(groupname string) (*Group, error) { 165 f, err := os.Open(groupFile) 166 if err != nil { 167 return nil, err 168 } 169 defer f.Close() 170 return findGroupName(groupname, f) 171 } 172 173 func lookupGroupId(id string) (*Group, error) { 174 f, err := os.Open(groupFile) 175 if err != nil { 176 return nil, err 177 } 178 defer f.Close() 179 return findGroupId(id, f) 180 } 181 182 func lookupUser(username string) (*User, error) { 183 f, err := os.Open(userFile) 184 if err != nil { 185 return nil, err 186 } 187 defer f.Close() 188 return findUsername(username, f) 189 } 190 191 func lookupUserId(uid string) (*User, error) { 192 f, err := os.Open(userFile) 193 if err != nil { 194 return nil, err 195 } 196 defer f.Close() 197 return findUserId(uid, f) 198 }