Browse Source

process: add getgroups(), setgroups(), initgroups()

DRY the getuid(), getgid(), etc. functions while we're at it.
v0.9.4-release
Ben Noordhuis 12 years ago
parent
commit
3ece130ea2
  1. 37
      doc/api/process.markdown
  2. 306
      src/node.cc
  3. 41
      test/simple/test-process-getgroups.js

37
doc/api/process.markdown

@ -261,6 +261,43 @@ blocks while resolving it to a numerical ID.
} }
## process.getgroups()
Note: this function is only available on POSIX platforms (i.e. not Windows)
Returns an array with the supplementary group IDs. POSIX leaves it unspecified
if the effective group ID is included but node.js ensures it always is.
## process.setgroups(groups)
Note: this function is only available on POSIX platforms (i.e. not Windows)
Sets the supplementary group IDs. This is a privileged operation, meaning you
need to be root or have the CAP_SETGID capability.
The list can contain group IDs, group names or both.
## process.initgroups(user, extra_group)
Note: this function is only available on POSIX platforms (i.e. not Windows)
Reads /etc/group and initializes the group access list, using all groups of
which the user is a member. This is a privileged operation, meaning you need
to be root or have the CAP_SETGID capability.
`user` is a user name or user ID. `extra_group` is a group name or group ID.
Some care needs to be taken when dropping privileges. Example:
console.log(process.getgroups()); // [ 0 ]
process.initgroups('bnoordhuis', 1000); // switch user
console.log(process.getgroups()); // [ 27, 30, 46, 1000, 0 ]
process.setgid(1000); // drop root gid
console.log(process.getgroups()); // [ 27, 30, 46, 1000 ]
## process.version ## process.version
A compiled-in property that exposes `NODE_VERSION`. A compiled-in property that exposes `NODE_VERSION`.

306
src/node.cc

@ -147,12 +147,6 @@ static bool use_sni = true;
static bool use_sni = false; static bool use_sni = false;
#endif #endif
#ifdef __POSIX__
// Buffer for getpwnam_r(), getgrpam_r() and other misc callers; keep this
// scoped at file-level rather than method-level to avoid excess stack usage.
static char getbuf[PATH_MAX + 1];
#endif
// We need to notify V8 when we're idle so that it can run the garbage // We need to notify V8 when we're idle so that it can run the garbage
// collector. The interface to this is V8::IdleNotification(). It returns // collector. The interface to this is V8::IdleNotification(). It returns
// true if the heap hasn't be fully compacted, and needs to be run again. // true if the heap hasn't be fully compacted, and needs to be run again.
@ -1467,6 +1461,108 @@ static Handle<Value> Umask(const Arguments& args) {
#ifdef __POSIX__ #ifdef __POSIX__
static const uid_t uid_not_found = static_cast<uid_t>(-1);
static const gid_t gid_not_found = static_cast<gid_t>(-1);
static uid_t uid_by_name(const char* name) {
struct passwd pwd;
struct passwd* pp;
char buf[8192];
int rc;
errno = 0;
pp = NULL;
if ((rc = getpwnam_r(name, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != NULL) {
return pp->pw_uid;
}
return uid_not_found;
}
static char* name_by_uid(uid_t uid) {
struct passwd pwd;
struct passwd* pp;
char buf[8192];
int rc;
errno = 0;
pp = NULL;
if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != NULL) {
return strdup(pp->pw_name);
}
if (rc == 0) {
errno = ENOENT;
}
return NULL;
}
static gid_t gid_by_name(const char* name) {
struct group pwd;
struct group* pp;
char buf[8192];
int rc;
errno = 0;
pp = NULL;
if ((rc = getgrnam_r(name, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != NULL) {
return pp->gr_gid;
}
return gid_not_found;
}
#if 0 // For future use.
static const char* name_by_gid(gid_t gid) {
struct group pwd;
struct group* pp;
char buf[8192];
int rc;
errno = 0;
pp = NULL;
if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != NULL) {
return strdup(pp->gr_name);
}
if (rc == 0) {
errno = ENOENT;
}
return NULL;
}
#endif
static uid_t uid_by_name(Handle<Value> value) {
if (value->IsUint32()) {
return static_cast<uid_t>(value->Uint32Value());
} else {
String::Utf8Value name(value);
return uid_by_name(*name);
}
}
static gid_t gid_by_name(Handle<Value> value) {
if (value->IsUint32()) {
return static_cast<gid_t>(value->Uint32Value());
} else {
String::Utf8Value name(value);
return gid_by_name(*name);
}
}
static Handle<Value> GetUid(const Arguments& args) { static Handle<Value> GetUid(const Arguments& args) {
HandleScope scope; HandleScope scope;
int uid = getuid(); int uid = getuid();
@ -1484,40 +1580,20 @@ static Handle<Value> GetGid(const Arguments& args) {
static Handle<Value> SetGid(const Arguments& args) { static Handle<Value> SetGid(const Arguments& args) {
HandleScope scope; HandleScope scope;
if (args.Length() < 1) { if (!args[0]->IsUint32() && !args[0]->IsString()) {
return ThrowException(Exception::Error( return ThrowTypeError("setgid argument must be a number or a string");
String::New("setgid requires 1 argument"))); }
}
int gid;
if (args[0]->IsNumber()) {
gid = args[0]->Int32Value();
} else if (args[0]->IsString()) {
String::Utf8Value grpnam(args[0]);
struct group grp, *grpp = NULL;
int err;
errno = 0;
if ((err = getgrnam_r(*grpnam, &grp, getbuf, ARRAY_SIZE(getbuf), &grpp)) ||
grpp == NULL) {
if (errno == 0)
return ThrowException(Exception::Error(
String::New("setgid group id does not exist")));
else
return ThrowException(ErrnoException(errno, "getgrnam_r"));
}
gid = grpp->gr_gid; gid_t gid = gid_by_name(args[0]);
} else {
return ThrowException(Exception::Error( if (gid == gid_not_found) {
String::New("setgid argument must be a number or a string"))); return ThrowError("setgid group id does not exist");
} }
int result; if (setgid(gid)) {
if ((result = setgid(gid)) != 0) {
return ThrowException(ErrnoException(errno, "setgid")); return ThrowException(ErrnoException(errno, "setgid"));
} }
return Undefined(); return Undefined();
} }
@ -1525,44 +1601,142 @@ static Handle<Value> SetGid(const Arguments& args) {
static Handle<Value> SetUid(const Arguments& args) { static Handle<Value> SetUid(const Arguments& args) {
HandleScope scope; HandleScope scope;
if (args.Length() < 1) { if (!args[0]->IsUint32() && !args[0]->IsString()) {
return ThrowException(Exception::Error( return ThrowTypeError("setuid argument must be a number or a string");
String::New("setuid requires 1 argument"))); }
}
int uid;
if (args[0]->IsNumber()) {
uid = args[0]->Int32Value();
} else if (args[0]->IsString()) {
String::Utf8Value pwnam(args[0]);
struct passwd pwd, *pwdp = NULL;
int err;
errno = 0;
if ((err = getpwnam_r(*pwnam, &pwd, getbuf, ARRAY_SIZE(getbuf), &pwdp)) ||
pwdp == NULL) {
if (errno == 0)
return ThrowException(Exception::Error(
String::New("setuid user id does not exist")));
else
return ThrowException(ErrnoException(errno, "getpwnam_r"));
}
uid = pwdp->pw_uid; uid_t uid = uid_by_name(args[0]);
} else {
return ThrowException(Exception::Error( if (uid == uid_not_found) {
String::New("setuid argument must be a number or a string"))); return ThrowError("setuid user id does not exist");
} }
int result; if (setuid(uid)) {
if ((result = setuid(uid)) != 0) {
return ThrowException(ErrnoException(errno, "setuid")); return ThrowException(ErrnoException(errno, "setuid"));
} }
return Undefined();
}
static Handle<Value> GetGroups(const Arguments& args) {
HandleScope scope;
int ngroups = getgroups(0, NULL);
if (ngroups == -1) {
return ThrowException(ErrnoException(errno, "getgroups"));
}
gid_t* groups = new gid_t[ngroups];
ngroups = getgroups(ngroups, groups);
if (ngroups == -1) {
delete[] groups;
return ThrowException(ErrnoException(errno, "getgroups"));
}
Local<Array> groups_list = Array::New(ngroups);
bool seen_egid = false;
gid_t egid = getegid();
for (int i = 0; i < ngroups; i++) {
groups_list->Set(i, Integer::New(groups[i]));
if (groups[i] == egid) seen_egid = true;
}
delete[] groups;
if (seen_egid == false) {
groups_list->Set(ngroups, Integer::New(egid));
}
return scope.Close(groups_list);
}
static Handle<Value> SetGroups(const Arguments& args) {
HandleScope scope;
if (!args[0]->IsArray()) {
return ThrowTypeError("argument 1 must be an array");
}
Local<Array> groups_list = args[0].As<Array>();
size_t size = groups_list->Length();
gid_t* groups = new gid_t[size];
for (size_t i = 0; i < size; i++) {
gid_t gid = gid_by_name(groups_list->Get(i));
if (gid == gid_not_found) {
delete[] groups;
return ThrowError("group name not found");
}
groups[i] = gid;
}
int rc = setgroups(size, groups);
delete[] groups;
if (rc == -1) {
return ThrowException(ErrnoException(errno, "setgroups"));
}
return Undefined(); return Undefined();
} }
static Handle<Value> InitGroups(const Arguments& args) {
HandleScope scope;
if (!args[0]->IsUint32() && !args[0]->IsString()) {
return ThrowTypeError("argument 1 must be a number or a string");
}
if (!args[1]->IsUint32() && !args[1]->IsString()) {
return ThrowTypeError("argument 2 must be a number or a string");
}
String::Utf8Value arg0(args[0]);
gid_t extra_group;
bool must_free;
char* user;
if (args[0]->IsUint32()) {
user = name_by_uid(args[0]->Uint32Value());
must_free = true;
} else {
user = *arg0;
must_free = false;
}
if (user == NULL) {
return ThrowError("initgroups user not found");
}
extra_group = gid_by_name(args[1]);
if (extra_group == gid_not_found) {
if (must_free) free(user);
return ThrowError("initgroups extra group not found");
}
int rc = initgroups(user, extra_group);
if (must_free) {
free(user);
}
if (rc) {
return ThrowException(ErrnoException(errno, "initgroups"));
}
return Undefined();
}
#endif // __POSIX__ #endif // __POSIX__
@ -2250,6 +2424,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
NODE_SET_METHOD(process, "setgid", SetGid); NODE_SET_METHOD(process, "setgid", SetGid);
NODE_SET_METHOD(process, "getgid", GetGid); NODE_SET_METHOD(process, "getgid", GetGid);
NODE_SET_METHOD(process, "getgroups", GetGroups);
NODE_SET_METHOD(process, "setgroups", SetGroups);
NODE_SET_METHOD(process, "initgroups", InitGroups);
#endif // __POSIX__ #endif // __POSIX__
NODE_SET_METHOD(process, "_kill", Kill); NODE_SET_METHOD(process, "_kill", Kill);

41
test/simple/test-process-getgroups.js

@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var exec = require('child_process').exec;
if (typeof process.getgroups === 'function') {
var groups = process.getgroups();
assert(Array.isArray(groups));
assert(groups.length > 0);
exec('id -G', function(err, stdout) {
if (err) throw err;
var real_groups = stdout.match(/\d+/g).map(Number);
assert.equal(groups.length, real_groups.length);
check(groups, real_groups);
check(real_groups, groups);
});
}
function check(a, b) {
for (var i = 0; i < a.length; ++i) assert(b.indexOf(a[i]) !== -1);
}
Loading…
Cancel
Save