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