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