github.com/opensuse/umoci@v0.4.2/third_party/user/user.go (about) 1 /* 2 * Imported from opencontainers/runc/libcontainer/user. 3 * Copyright (C) 2014 Docker, Inc. 4 * Copyright (C) The Linux Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package user 20 21 import ( 22 "bufio" 23 "fmt" 24 "io" 25 "os" 26 "strconv" 27 "strings" 28 ) 29 30 const ( 31 minId = 0 32 maxId = 1<<31 - 1 //for 32-bit systems compatibility 33 ) 34 35 var ( 36 ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId) 37 ) 38 39 type User struct { 40 Name string 41 Pass string 42 Uid int 43 Gid int 44 Gecos string 45 Home string 46 Shell string 47 } 48 49 type Group struct { 50 Name string 51 Pass string 52 Gid int 53 List []string 54 } 55 56 func parseLine(line string, v ...interface{}) { 57 if line == "" { 58 return 59 } 60 61 parts := strings.Split(line, ":") 62 for i, p := range parts { 63 // Ignore cases where we don't have enough fields to populate the arguments. 64 // Some configuration files like to misbehave. 65 if len(v) <= i { 66 break 67 } 68 69 // Use the type of the argument to figure out how to parse it, scanf() style. 70 // This is legit. 71 switch e := v[i].(type) { 72 case *string: 73 *e = p 74 case *int: 75 // "numbers", with conversion errors ignored because of some misbehaving configuration files. 76 *e, _ = strconv.Atoi(p) 77 case *[]string: 78 // Comma-separated lists. 79 if p != "" { 80 *e = strings.Split(p, ",") 81 } else { 82 *e = []string{} 83 } 84 default: 85 // Someone goof'd when writing code using this function. Scream so they can hear us. 86 panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e)) 87 } 88 } 89 } 90 91 func ParsePasswdFile(path string) ([]User, error) { 92 passwd, err := os.Open(path) 93 if err != nil { 94 return nil, err 95 } 96 defer passwd.Close() 97 return ParsePasswd(passwd) 98 } 99 100 func ParsePasswd(passwd io.Reader) ([]User, error) { 101 return ParsePasswdFilter(passwd, nil) 102 } 103 104 func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { 105 passwd, err := os.Open(path) 106 if err != nil { 107 return nil, err 108 } 109 defer passwd.Close() 110 return ParsePasswdFilter(passwd, filter) 111 } 112 113 func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { 114 if r == nil { 115 return nil, fmt.Errorf("nil source for passwd-formatted data") 116 } 117 118 var ( 119 s = bufio.NewScanner(r) 120 out = []User{} 121 ) 122 123 for s.Scan() { 124 if err := s.Err(); err != nil { 125 return nil, err 126 } 127 128 line := strings.TrimSpace(s.Text()) 129 if line == "" { 130 continue 131 } 132 133 // see: man 5 passwd 134 // name:password:UID:GID:GECOS:directory:shell 135 // Name:Pass:Uid:Gid:Gecos:Home:Shell 136 // root:x:0:0:root:/root:/bin/bash 137 // adm:x:3:4:adm:/var/adm:/bin/false 138 p := User{} 139 parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell) 140 141 if filter == nil || filter(p) { 142 out = append(out, p) 143 } 144 } 145 146 return out, nil 147 } 148 149 func ParseGroupFile(path string) ([]Group, error) { 150 group, err := os.Open(path) 151 if err != nil { 152 return nil, err 153 } 154 155 defer group.Close() 156 return ParseGroup(group) 157 } 158 159 func ParseGroup(group io.Reader) ([]Group, error) { 160 return ParseGroupFilter(group, nil) 161 } 162 163 func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { 164 group, err := os.Open(path) 165 if err != nil { 166 return nil, err 167 } 168 defer group.Close() 169 return ParseGroupFilter(group, filter) 170 } 171 172 func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { 173 if r == nil { 174 return nil, fmt.Errorf("nil source for group-formatted data") 175 } 176 177 var ( 178 s = bufio.NewScanner(r) 179 out = []Group{} 180 ) 181 182 for s.Scan() { 183 if err := s.Err(); err != nil { 184 return nil, err 185 } 186 187 text := s.Text() 188 if text == "" { 189 continue 190 } 191 192 // see: man 5 group 193 // group_name:password:GID:user_list 194 // Name:Pass:Gid:List 195 // root:x:0:root 196 // adm:x:4:root,adm,daemon 197 p := Group{} 198 parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List) 199 200 if filter == nil || filter(p) { 201 out = append(out, p) 202 } 203 } 204 205 return out, nil 206 } 207 208 type ExecUser struct { 209 Uid int 210 Gid int 211 Sgids []int 212 Home string 213 } 214 215 // GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the 216 // given file paths and uses that data as the arguments to GetExecUser. If the 217 // files cannot be opened for any reason, the error is ignored and a nil 218 // io.Reader is passed instead. 219 func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { 220 passwd, err := os.Open(passwdPath) 221 if err != nil { 222 passwd = nil 223 } else { 224 defer passwd.Close() 225 } 226 227 group, err := os.Open(groupPath) 228 if err != nil { 229 group = nil 230 } else { 231 defer group.Close() 232 } 233 234 return GetExecUser(userSpec, defaults, passwd, group) 235 } 236 237 // GetExecUser parses a user specification string (using the passwd and group 238 // readers as sources for /etc/passwd and /etc/group data, respectively). In 239 // the case of blank fields or missing data from the sources, the values in 240 // defaults is used. 241 // 242 // GetExecUser will return an error if a user or group literal could not be 243 // found in any entry in passwd and group respectively. 244 // 245 // Examples of valid user specifications are: 246 // * "" 247 // * "user" 248 // * "uid" 249 // * "user:group" 250 // * "uid:gid 251 // * "user:gid" 252 // * "uid:group" 253 // 254 // It should be noted that if you specify a numeric user or group id, they will 255 // not be evaluated as usernames (only the metadata will be filled). So attempting 256 // to parse a user with user.Name = "1337" will produce the user with a UID of 257 // 1337. 258 func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { 259 if defaults == nil { 260 defaults = new(ExecUser) 261 } 262 263 // Copy over defaults. 264 user := &ExecUser{ 265 Uid: defaults.Uid, 266 Gid: defaults.Gid, 267 Sgids: defaults.Sgids, 268 Home: defaults.Home, 269 } 270 271 // Sgids slice *cannot* be nil. 272 if user.Sgids == nil { 273 user.Sgids = []int{} 274 } 275 276 // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax 277 var userArg, groupArg string 278 parseLine(userSpec, &userArg, &groupArg) 279 280 // Convert userArg and groupArg to be numeric, so we don't have to execute 281 // Atoi *twice* for each iteration over lines. 282 uidArg, uidErr := strconv.Atoi(userArg) 283 gidArg, gidErr := strconv.Atoi(groupArg) 284 285 // Find the matching user. 286 users, err := ParsePasswdFilter(passwd, func(u User) bool { 287 if userArg == "" { 288 // Default to current state of the user. 289 return u.Uid == user.Uid 290 } 291 292 if uidErr == nil { 293 // If the userArg is numeric, always treat it as a UID. 294 return uidArg == u.Uid 295 } 296 297 return u.Name == userArg 298 }) 299 300 // If we can't find the user, we have to bail. 301 if err != nil && passwd != nil { 302 if userArg == "" { 303 userArg = strconv.Itoa(user.Uid) 304 } 305 return nil, fmt.Errorf("unable to find user %s: %v", userArg, err) 306 } 307 308 var matchedUserName string 309 if len(users) > 0 { 310 // First match wins, even if there's more than one matching entry. 311 matchedUserName = users[0].Name 312 user.Uid = users[0].Uid 313 user.Gid = users[0].Gid 314 user.Home = users[0].Home 315 } else if userArg != "" { 316 // If we can't find a user with the given username, the only other valid 317 // option is if it's a numeric username with no associated entry in passwd. 318 319 if uidErr != nil { 320 // Not numeric. 321 return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries) 322 } 323 user.Uid = uidArg 324 325 // Must be inside valid uid range. 326 if user.Uid < minId || user.Uid > maxId { 327 return nil, ErrRange 328 } 329 330 // Okay, so it's numeric. We can just roll with this. 331 } 332 333 // On to the groups. If we matched a username, we need to do this because of 334 // the supplementary group IDs. 335 if groupArg != "" || matchedUserName != "" { 336 groups, err := ParseGroupFilter(group, func(g Group) bool { 337 // If the group argument isn't explicit, we'll just search for it. 338 if groupArg == "" { 339 // Check if user is a member of this group. 340 for _, u := range g.List { 341 if u == matchedUserName { 342 return true 343 } 344 } 345 return false 346 } 347 348 if gidErr == nil { 349 // If the groupArg is numeric, always treat it as a GID. 350 return gidArg == g.Gid 351 } 352 353 return g.Name == groupArg 354 }) 355 if err != nil && group != nil { 356 return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err) 357 } 358 359 // Only start modifying user.Gid if it is in explicit form. 360 if groupArg != "" { 361 if len(groups) > 0 { 362 // First match wins, even if there's more than one matching entry. 363 user.Gid = groups[0].Gid 364 } else if groupArg != "" { 365 // If we can't find a group with the given name, the only other valid 366 // option is if it's a numeric group name with no associated entry in group. 367 368 if gidErr != nil { 369 // Not numeric. 370 return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries) 371 } 372 user.Gid = gidArg 373 374 // Must be inside valid gid range. 375 if user.Gid < minId || user.Gid > maxId { 376 return nil, ErrRange 377 } 378 379 // Okay, so it's numeric. We can just roll with this. 380 } 381 } else if len(groups) > 0 && uidErr != nil { 382 // Supplementary group ids only make sense if in the implicit form. 383 user.Sgids = make([]int, len(groups)) 384 for i, group := range groups { 385 user.Sgids[i] = group.Gid 386 } 387 } 388 } 389 390 return user, nil 391 } 392 393 // GetAdditionalGroups looks up a list of groups by name or group id 394 // against the given /etc/group formatted data. If a group name cannot 395 // be found, an error will be returned. If a group id cannot be found, 396 // or the given group data is nil, the id will be returned as-is 397 // provided it is in the legal range. 398 func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) { 399 var groups = []Group{} 400 if group != nil { 401 var err error 402 groups, err = ParseGroupFilter(group, func(g Group) bool { 403 for _, ag := range additionalGroups { 404 if g.Name == ag || strconv.Itoa(g.Gid) == ag { 405 return true 406 } 407 } 408 return false 409 }) 410 if err != nil { 411 return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err) 412 } 413 } 414 415 gidMap := make(map[int]struct{}) 416 for _, ag := range additionalGroups { 417 var found bool 418 for _, g := range groups { 419 // if we found a matched group either by name or gid, take the 420 // first matched as correct 421 if g.Name == ag || strconv.Itoa(g.Gid) == ag { 422 if _, ok := gidMap[g.Gid]; !ok { 423 gidMap[g.Gid] = struct{}{} 424 found = true 425 break 426 } 427 } 428 } 429 // we asked for a group but didn't find it. let's check to see 430 // if we wanted a numeric group 431 if !found { 432 gid, err := strconv.Atoi(ag) 433 if err != nil { 434 return nil, fmt.Errorf("Unable to find group %s", ag) 435 } 436 // Ensure gid is inside gid range. 437 if gid < minId || gid > maxId { 438 return nil, ErrRange 439 } 440 gidMap[gid] = struct{}{} 441 } 442 } 443 gids := []int{} 444 for gid := range gidMap { 445 gids = append(gids, gid) 446 } 447 return gids, nil 448 } 449 450 // GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups 451 // that opens the groupPath given and gives it as an argument to 452 // GetAdditionalGroups. 453 func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) { 454 group, err := os.Open(groupPath) 455 if err == nil { 456 defer group.Close() 457 } 458 return GetAdditionalGroups(additionalGroups, group) 459 }