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