blob: 523269086e68f8ab32ccf8d930be68c55ab60f98 [file] [log] [blame]
Dan Willemsend2797482017-07-26 13:13:13 -07001// 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
Dan Willemsencc753b72021-08-31 13:25:42 -07005//go:build (aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris) && cgo && !osusergo
Dan Willemsend2797482017-07-26 13:13:13 -07006
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
Dan Willemsena3223282018-02-27 19:41:43 -080054 nameC := make([]byte, len(username)+1)
55 copy(nameC, username)
Dan Willemsend2797482017-07-26 13:13:13 -070056
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.
Dan Willemsena3223282018-02-27 19:41:43 -080065 return syscall.Errno(C.mygetpwnam_r((*C.char)(unsafe.Pointer(&nameC[0])),
Dan Willemsend2797482017-07-26 13:13:13 -070066 &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 {
Colin Crossd9c6b802019-03-19 21:10:31 -070096 // 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.
Dan Willemsend2797482017-07-26 13:13:13 -070098 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{
Dan Willemsena3223282018-02-27 19:41:43 -0800115 Uid: strconv.FormatUint(uint64(pwd.pw_uid), 10),
116 Gid: strconv.FormatUint(uint64(pwd.pw_gid), 10),
Dan Willemsend2797482017-07-26 13:13:13 -0700117 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."
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800125 u.Name, _, _ = strings.Cut(u.Name, ",")
Dan Willemsend2797482017-07-26 13:13:13 -0700126 return u
127}
128
Dan Willemsend2797482017-07-26 13:13:13 -0700129func 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()
Dan Willemsena3223282018-02-27 19:41:43 -0800135 cname := make([]byte, len(groupname)+1)
136 copy(cname, groupname)
Dan Willemsend2797482017-07-26 13:13:13 -0700137
138 err := retryWithBuffer(buf, func() syscall.Errno {
Dan Willemsena3223282018-02-27 19:41:43 -0800139 return syscall.Errno(C.mygetgrnam_r((*C.char)(unsafe.Pointer(&cname[0])),
Dan Willemsend2797482017-07-26 13:13:13 -0700140 &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 {
Colin Crossd9c6b802019-03-19 21:10:31 -0700170 // 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.
Dan Willemsend2797482017-07-26 13:13:13 -0700172 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}
Dan Willemsena3223282018-02-27 19:41:43 -0800263
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}