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 (aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris) && cgo && !osusergo
6
7package user
8
9import (
10	"fmt"
11	"strconv"
12	"strings"
13	"syscall"
14	"unsafe"
15)
16
17/*
18#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
19#include <unistd.h>
20#include <sys/types.h>
21#include <pwd.h>
22#include <grp.h>
23#include <stdlib.h>
24
25static int mygetpwuid_r(int uid, struct passwd *pwd,
26	char *buf, size_t buflen, struct passwd **result) {
27	return getpwuid_r(uid, pwd, buf, buflen, result);
28}
29
30static int mygetpwnam_r(const char *name, struct passwd *pwd,
31	char *buf, size_t buflen, struct passwd **result) {
32	return getpwnam_r(name, pwd, buf, buflen, result);
33}
34
35static int mygetgrgid_r(int gid, struct group *grp,
36	char *buf, size_t buflen, struct group **result) {
37 return getgrgid_r(gid, grp, buf, buflen, result);
38}
39
40static int mygetgrnam_r(const char *name, struct group *grp,
41	char *buf, size_t buflen, struct group **result) {
42 return getgrnam_r(name, grp, buf, buflen, result);
43}
44*/
45import "C"
46
47func current() (*User, error) {
48	return lookupUnixUid(syscall.Getuid())
49}
50
51func lookupUser(username string) (*User, error) {
52	var pwd C.struct_passwd
53	var result *C.struct_passwd
54	nameC := make([]byte, len(username)+1)
55	copy(nameC, username)
56
57	buf := alloc(userBuffer)
58	defer buf.free()
59
60	err := retryWithBuffer(buf, func() syscall.Errno {
61		// mygetpwnam_r is a wrapper around getpwnam_r to avoid
62		// passing a size_t to getpwnam_r, because for unknown
63		// reasons passing a size_t to getpwnam_r doesn't work on
64		// Solaris.
65		return syscall.Errno(C.mygetpwnam_r((*C.char)(unsafe.Pointer(&nameC[0])),
66			&pwd,
67			(*C.char)(buf.ptr),
68			C.size_t(buf.size),
69			&result))
70	})
71	if err != nil {
72		return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
73	}
74	if result == nil {
75		return nil, UnknownUserError(username)
76	}
77	return buildUser(&pwd), err
78}
79
80func lookupUserId(uid string) (*User, error) {
81	i, e := strconv.Atoi(uid)
82	if e != nil {
83		return nil, e
84	}
85	return lookupUnixUid(i)
86}
87
88func lookupUnixUid(uid int) (*User, error) {
89	var pwd C.struct_passwd
90	var result *C.struct_passwd
91
92	buf := alloc(userBuffer)
93	defer buf.free()
94
95	err := retryWithBuffer(buf, func() syscall.Errno {
96		// mygetpwuid_r is a wrapper around getpwuid_r to avoid using uid_t
97		// because C.uid_t(uid) for unknown reasons doesn't work on linux.
98		return syscall.Errno(C.mygetpwuid_r(C.int(uid),
99			&pwd,
100			(*C.char)(buf.ptr),
101			C.size_t(buf.size),
102			&result))
103	})
104	if err != nil {
105		return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
106	}
107	if result == nil {
108		return nil, UnknownUserIdError(uid)
109	}
110	return buildUser(&pwd), nil
111}
112
113func buildUser(pwd *C.struct_passwd) *User {
114	u := &User{
115		Uid:      strconv.FormatUint(uint64(pwd.pw_uid), 10),
116		Gid:      strconv.FormatUint(uint64(pwd.pw_gid), 10),
117		Username: C.GoString(pwd.pw_name),
118		Name:     C.GoString(pwd.pw_gecos),
119		HomeDir:  C.GoString(pwd.pw_dir),
120	}
121	// The pw_gecos field isn't quite standardized. Some docs
122	// say: "It is expected to be a comma separated list of
123	// personal data where the first item is the full name of the
124	// user."
125	u.Name, _, _ = strings.Cut(u.Name, ",")
126	return u
127}
128
129func lookupGroup(groupname string) (*Group, error) {
130	var grp C.struct_group
131	var result *C.struct_group
132
133	buf := alloc(groupBuffer)
134	defer buf.free()
135	cname := make([]byte, len(groupname)+1)
136	copy(cname, groupname)
137
138	err := retryWithBuffer(buf, func() syscall.Errno {
139		return syscall.Errno(C.mygetgrnam_r((*C.char)(unsafe.Pointer(&cname[0])),
140			&grp,
141			(*C.char)(buf.ptr),
142			C.size_t(buf.size),
143			&result))
144	})
145	if err != nil {
146		return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
147	}
148	if result == nil {
149		return nil, UnknownGroupError(groupname)
150	}
151	return buildGroup(&grp), nil
152}
153
154func lookupGroupId(gid string) (*Group, error) {
155	i, e := strconv.Atoi(gid)
156	if e != nil {
157		return nil, e
158	}
159	return lookupUnixGid(i)
160}
161
162func lookupUnixGid(gid int) (*Group, error) {
163	var grp C.struct_group
164	var result *C.struct_group
165
166	buf := alloc(groupBuffer)
167	defer buf.free()
168
169	err := retryWithBuffer(buf, func() syscall.Errno {
170		// mygetgrgid_r is a wrapper around getgrgid_r to avoid using gid_t
171		// because C.gid_t(gid) for unknown reasons doesn't work on linux.
172		return syscall.Errno(C.mygetgrgid_r(C.int(gid),
173			&grp,
174			(*C.char)(buf.ptr),
175			C.size_t(buf.size),
176			&result))
177	})
178	if err != nil {
179		return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
180	}
181	if result == nil {
182		return nil, UnknownGroupIdError(strconv.Itoa(gid))
183	}
184	return buildGroup(&grp), nil
185}
186
187func buildGroup(grp *C.struct_group) *Group {
188	g := &Group{
189		Gid:  strconv.Itoa(int(grp.gr_gid)),
190		Name: C.GoString(grp.gr_name),
191	}
192	return g
193}
194
195type bufferKind C.int
196
197const (
198	userBuffer  = bufferKind(C._SC_GETPW_R_SIZE_MAX)
199	groupBuffer = bufferKind(C._SC_GETGR_R_SIZE_MAX)
200)
201
202func (k bufferKind) initialSize() C.size_t {
203	sz := C.sysconf(C.int(k))
204	if sz == -1 {
205		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
206		// Additionally, not all Linux systems have it, either. For
207		// example, the musl libc returns -1.
208		return 1024
209	}
210	if !isSizeReasonable(int64(sz)) {
211		// Truncate.  If this truly isn't enough, retryWithBuffer will error on the first run.
212		return maxBufferSize
213	}
214	return C.size_t(sz)
215}
216
217type memBuffer struct {
218	ptr  unsafe.Pointer
219	size C.size_t
220}
221
222func alloc(kind bufferKind) *memBuffer {
223	sz := kind.initialSize()
224	return &memBuffer{
225		ptr:  C.malloc(sz),
226		size: sz,
227	}
228}
229
230func (mb *memBuffer) resize(newSize C.size_t) {
231	mb.ptr = C.realloc(mb.ptr, newSize)
232	mb.size = newSize
233}
234
235func (mb *memBuffer) free() {
236	C.free(mb.ptr)
237}
238
239// retryWithBuffer repeatedly calls f(), increasing the size of the
240// buffer each time, until f succeeds, fails with a non-ERANGE error,
241// or the buffer exceeds a reasonable limit.
242func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error {
243	for {
244		errno := f()
245		if errno == 0 {
246			return nil
247		} else if errno != syscall.ERANGE {
248			return errno
249		}
250		newSize := buf.size * 2
251		if !isSizeReasonable(int64(newSize)) {
252			return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
253		}
254		buf.resize(newSize)
255	}
256}
257
258const maxBufferSize = 1 << 20
259
260func isSizeReasonable(sz int64) bool {
261	return sz > 0 && sz <= maxBufferSize
262}
263
264// Because we can't use cgo in tests:
265func structPasswdForNegativeTest() C.struct_passwd {
266	sp := C.struct_passwd{}
267	sp.pw_uid = 1<<32 - 2
268	sp.pw_gid = 1<<32 - 3
269	return sp
270}
271