1// Copyright 2011 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 (cgo || darwin) && !osusergo && unix && !android
6
7package user
8
9import (
10	"fmt"
11	"runtime"
12	"strconv"
13	"strings"
14	"syscall"
15	"unsafe"
16)
17
18func current() (*User, error) {
19	return lookupUnixUid(syscall.Getuid())
20}
21
22func lookupUser(username string) (*User, error) {
23	var pwd _C_struct_passwd
24	var found bool
25	nameC := make([]byte, len(username)+1)
26	copy(nameC, username)
27
28	err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
29		var errno syscall.Errno
30		pwd, found, errno = _C_getpwnam_r((*_C_char)(unsafe.Pointer(&nameC[0])),
31			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
32		return errno
33	})
34	if err == syscall.ENOENT || (err == nil && !found) {
35		return nil, UnknownUserError(username)
36	}
37	if err != nil {
38		return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
39	}
40	return buildUser(&pwd), err
41}
42
43func lookupUserId(uid string) (*User, error) {
44	i, e := strconv.Atoi(uid)
45	if e != nil {
46		return nil, e
47	}
48	return lookupUnixUid(i)
49}
50
51func lookupUnixUid(uid int) (*User, error) {
52	var pwd _C_struct_passwd
53	var found bool
54
55	err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
56		var errno syscall.Errno
57		pwd, found, errno = _C_getpwuid_r(_C_uid_t(uid),
58			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
59		return errno
60	})
61	if err == syscall.ENOENT || (err == nil && !found) {
62		return nil, UnknownUserIdError(uid)
63	}
64	if err != nil {
65		return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
66	}
67	return buildUser(&pwd), nil
68}
69
70func buildUser(pwd *_C_struct_passwd) *User {
71	u := &User{
72		Uid:      strconv.FormatUint(uint64(_C_pw_uid(pwd)), 10),
73		Gid:      strconv.FormatUint(uint64(_C_pw_gid(pwd)), 10),
74		Username: _C_GoString(_C_pw_name(pwd)),
75		Name:     _C_GoString(_C_pw_gecos(pwd)),
76		HomeDir:  _C_GoString(_C_pw_dir(pwd)),
77	}
78	// The pw_gecos field isn't quite standardized. Some docs
79	// say: "It is expected to be a comma separated list of
80	// personal data where the first item is the full name of the
81	// user."
82	u.Name, _, _ = strings.Cut(u.Name, ",")
83	return u
84}
85
86func lookupGroup(groupname string) (*Group, error) {
87	var grp _C_struct_group
88	var found bool
89
90	cname := make([]byte, len(groupname)+1)
91	copy(cname, groupname)
92
93	err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
94		var errno syscall.Errno
95		grp, found, errno = _C_getgrnam_r((*_C_char)(unsafe.Pointer(&cname[0])),
96			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
97		return errno
98	})
99	if err == syscall.ENOENT || (err == nil && !found) {
100		return nil, UnknownGroupError(groupname)
101	}
102	if err != nil {
103		return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
104	}
105	return buildGroup(&grp), nil
106}
107
108func lookupGroupId(gid string) (*Group, error) {
109	i, e := strconv.Atoi(gid)
110	if e != nil {
111		return nil, e
112	}
113	return lookupUnixGid(i)
114}
115
116func lookupUnixGid(gid int) (*Group, error) {
117	var grp _C_struct_group
118	var found bool
119
120	err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
121		var errno syscall.Errno
122		grp, found, errno = _C_getgrgid_r(_C_gid_t(gid),
123			(*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
124		return syscall.Errno(errno)
125	})
126	if err == syscall.ENOENT || (err == nil && !found) {
127		return nil, UnknownGroupIdError(strconv.Itoa(gid))
128	}
129	if err != nil {
130		return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
131	}
132	return buildGroup(&grp), nil
133}
134
135func buildGroup(grp *_C_struct_group) *Group {
136	g := &Group{
137		Gid:  strconv.Itoa(int(_C_gr_gid(grp))),
138		Name: _C_GoString(_C_gr_name(grp)),
139	}
140	return g
141}
142
143type bufferKind _C_int
144
145var (
146	userBuffer  = bufferKind(_C__SC_GETPW_R_SIZE_MAX)
147	groupBuffer = bufferKind(_C__SC_GETGR_R_SIZE_MAX)
148)
149
150func (k bufferKind) initialSize() _C_size_t {
151	sz := _C_sysconf(_C_int(k))
152	if sz == -1 {
153		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
154		// Additionally, not all Linux systems have it, either. For
155		// example, the musl libc returns -1.
156		return 1024
157	}
158	if !isSizeReasonable(int64(sz)) {
159		// Truncate.  If this truly isn't enough, retryWithBuffer will error on the first run.
160		return maxBufferSize
161	}
162	return _C_size_t(sz)
163}
164
165// retryWithBuffer repeatedly calls f(), increasing the size of the
166// buffer each time, until f succeeds, fails with a non-ERANGE error,
167// or the buffer exceeds a reasonable limit.
168func retryWithBuffer(kind bufferKind, f func([]byte) syscall.Errno) error {
169	buf := make([]byte, kind.initialSize())
170	for {
171		errno := f(buf)
172		if errno == 0 {
173			return nil
174		} else if runtime.GOOS == "aix" && errno+1 == 0 {
175			// On AIX getpwuid_r appears to return -1,
176			// not ERANGE, on buffer overflow.
177		} else if errno != syscall.ERANGE {
178			return errno
179		}
180		newSize := len(buf) * 2
181		if !isSizeReasonable(int64(newSize)) {
182			return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
183		}
184		buf = make([]byte, newSize)
185	}
186}
187
188const maxBufferSize = 1 << 20
189
190func isSizeReasonable(sz int64) bool {
191	return sz > 0 && sz <= maxBufferSize
192}
193
194// Because we can't use cgo in tests:
195func structPasswdForNegativeTest() _C_struct_passwd {
196	sp := _C_struct_passwd{}
197	*_C_pw_uidp(&sp) = 1<<32 - 2
198	*_C_pw_gidp(&sp) = 1<<32 - 3
199	return sp
200}
201