...

Source file src/os/user/lookup_unix.go

     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 aix darwin dragonfly freebsd js,wasm !android,linux nacl netbsd openbsd solaris
     6	// +build !cgo osusergo
     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	}
   198	

View as plain text