Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
process: add getgroups(), setgroups(), initgroups()
Browse files Browse the repository at this point in the history
DRY the getuid(), getgid(), etc. functions while we're at it.
  • Loading branch information
bnoordhuis committed Dec 6, 2012
1 parent c08e947 commit 3ece130
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 64 deletions.
37 changes: 37 additions & 0 deletions doc/api/process.markdown
Expand Up @@ -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

A compiled-in property that exposes `NODE_VERSION`.
Expand Down
306 changes: 242 additions & 64 deletions src/node.cc
Expand Up @@ -147,12 +147,6 @@ static bool use_sni = true;
static bool use_sni = false;
#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
// 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.
Expand Down Expand Up @@ -1467,6 +1461,108 @@ static Handle<Value> Umask(const Arguments& args) {

#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) {
HandleScope scope;
int uid = getuid();
Expand All @@ -1484,85 +1580,163 @@ static Handle<Value> GetGid(const Arguments& args) {
static Handle<Value> SetGid(const Arguments& args) {
HandleScope scope;

if (args.Length() < 1) {
return ThrowException(Exception::Error(
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"));
}
if (!args[0]->IsUint32() && !args[0]->IsString()) {
return ThrowTypeError("setgid argument must be a number or a string");
}

gid = grpp->gr_gid;
} else {
return ThrowException(Exception::Error(
String::New("setgid argument must be a number or a string")));
gid_t gid = gid_by_name(args[0]);

if (gid == gid_not_found) {
return ThrowError("setgid group id does not exist");
}

int result;
if ((result = setgid(gid)) != 0) {
if (setgid(gid)) {
return ThrowException(ErrnoException(errno, "setgid"));
}

return Undefined();
}


static Handle<Value> SetUid(const Arguments& args) {
HandleScope scope;

if (args.Length() < 1) {
return ThrowException(Exception::Error(
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"));
}
if (!args[0]->IsUint32() && !args[0]->IsString()) {
return ThrowTypeError("setuid argument must be a number or a string");
}

uid = pwdp->pw_uid;
} else {
return ThrowException(Exception::Error(
String::New("setuid argument must be a number or a string")));
uid_t uid = uid_by_name(args[0]);

if (uid == uid_not_found) {
return ThrowError("setuid user id does not exist");
}

int result;
if ((result = setuid(uid)) != 0) {
if (setuid(uid)) {
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();
}


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__


Expand Down Expand Up @@ -2250,6 +2424,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {

NODE_SET_METHOD(process, "setgid", SetGid);
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__

NODE_SET_METHOD(process, "_kill", Kill);
Expand Down

0 comments on commit 3ece130

Please sign in to comment.