Browse Source

child_process: Support setting uid/gid by name

v0.7.4-release
isaacs 14 years ago
committed by Ryan Dahl
parent
commit
435ece5058
  1. 99
      src/node_child_process.cc
  2. 6
      src/node_child_process.h
  3. 46
      test/disabled/test-child-process-uid-gid.js

99
src/node_child_process.cc

@ -15,6 +15,8 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <pwd.h> /* getpwnam() */
#include <grp.h> /* getgrnam() */
#if defined(__FreeBSD__ ) || defined(__OpenBSD__) #if defined(__FreeBSD__ ) || defined(__OpenBSD__)
#include <sys/wait.h> #include <sys/wait.h>
#endif #endif
@ -26,6 +28,8 @@
extern char **environ; extern char **environ;
# endif # endif
#include <limits.h> /* PATH_MAX */
namespace node { namespace node {
using namespace v8; using namespace v8;
@ -100,7 +104,11 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
!args[0]->IsString() || !args[0]->IsString() ||
!args[1]->IsArray() || !args[1]->IsArray() ||
!args[2]->IsString() || !args[2]->IsString() ||
!args[3]->IsArray()) { !args[3]->IsArray() ||
!args[4]->IsArray() ||
!args[5]->IsBoolean() ||
!(args[6]->IsInt32() || args[6]->IsString()) ||
!(args[7]->IsInt32() || args[7]->IsString())) {
return ThrowException(Exception::Error(String::New("Bad argument."))); return ThrowException(Exception::Error(String::New("Bad argument.")));
} }
@ -158,8 +166,33 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
int fds[3]; int fds[3];
int uid = args[6]->ToInteger()->Value(); char *custom_uname = NULL;
int gid = args[7]->ToInteger()->Value(); int custom_uid = -1;
if (args[6]->IsNumber()) {
custom_uid = args[6]->Int32Value();
} else if (args[6]->IsString()) {
String::Utf8Value pwnam(args[6]->ToString());
custom_uname = (char *)calloc(sizeof(char), pwnam.length() + 1);
strncpy(custom_uname, *pwnam, pwnam.length() + 1);
} else {
return ThrowException(Exception::Error(
String::New("setuid argument must be a number or a string")));
}
char *custom_gname = NULL;
int custom_gid = -1;
if (args[7]->IsNumber()) {
custom_gid = args[7]->Int32Value();
} else if (args[7]->IsString()) {
String::Utf8Value grnam(args[7]->ToString());
custom_gname = (char *)calloc(sizeof(char), grnam.length() + 1);
strncpy(custom_gname, *grnam, grnam.length() + 1);
} else {
return ThrowException(Exception::Error(
String::New("setgid argument must be a number or a string")));
}
int r = child->Spawn(argv[0], int r = child->Spawn(argv[0],
argv, argv,
@ -168,8 +201,13 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
fds, fds,
custom_fds, custom_fds,
do_setsid, do_setsid,
uid, custom_uid,
gid); custom_uname,
custom_gid,
custom_gname);
if (custom_uname != NULL) free(custom_uname);
if (custom_gname != NULL) free(custom_gname);
for (i = 0; i < argv_length; i++) free(argv[i]); for (i = 0; i < argv_length; i++) free(argv[i]);
delete [] argv; delete [] argv;
@ -246,7 +284,9 @@ int ChildProcess::Spawn(const char *file,
int custom_fds[3], int custom_fds[3],
bool do_setsid, bool do_setsid,
int custom_uid, int custom_uid,
int custom_gid) { char *custom_uname,
int custom_gid,
char *custom_gname) {
HandleScope scope; HandleScope scope;
assert(pid_ == -1); assert(pid_ == -1);
assert(!ev_is_active(&child_watcher_)); assert(!ev_is_active(&child_watcher_));
@ -292,16 +332,59 @@ int ChildProcess::Spawn(const char *file,
_exit(127); _exit(127);
} }
if (custom_gid != -1 && setgid(custom_gid)) { static char buf[PATH_MAX + 1];
int gid = -1;
if (custom_gid != -1) {
gid = custom_gid;
} else if (custom_gname != NULL) {
struct group grp, *grpp = NULL;
int err = getgrnam_r(custom_gname,
&grp,
buf,
PATH_MAX + 1,
&grpp);
if (err || grpp == NULL) {
perror("getgrnam_r()");
_exit(127);
}
gid = grpp->gr_gid;
}
int uid = -1;
if (custom_uid != -1) {
uid = custom_uid;
} else if (custom_uname != NULL) {
struct passwd pwd, *pwdp = NULL;
int err = getpwnam_r(custom_uname,
&pwd,
buf,
PATH_MAX + 1,
&pwdp);
if (err || pwdp == NULL) {
perror("getpwnam_r()");
_exit(127);
}
uid = pwdp->pw_uid;
}
if (gid != -1 && setgid(gid)) {
perror("setgid()"); perror("setgid()");
_exit(127); _exit(127);
} }
if (custom_uid != -1 && setuid(custom_uid)) { if (uid != -1 && setuid(uid)) {
perror("setuid()"); perror("setuid()");
_exit(127); _exit(127);
} }
if (custom_fds[0] == -1) { if (custom_fds[0] == -1) {
close(stdin_pipe[1]); // close write end close(stdin_pipe[1]); // close write end
dup2(stdin_pipe[0], STDIN_FILENO); dup2(stdin_pipe[0], STDIN_FILENO);

6
src/node_child_process.h

@ -65,8 +65,10 @@ class ChildProcess : ObjectWrap {
int stdio_fds[3], int stdio_fds[3],
int custom_fds[3], int custom_fds[3],
bool do_setsid, bool do_setsid,
int uid, int custom_uid,
int gid); char *custom_uname,
int custom_gid,
char *custom_gname);
// Simple syscall wrapper. Does not disable the watcher. onexit will be // Simple syscall wrapper. Does not disable the watcher. onexit will be
// called still. // called still.

46
test/disabled/test-child-process-uid-gid.js

@ -1,13 +1,20 @@
// must be run as sudo, otherwise the gid/uid setting will fail. var assert = require("assert");
var child_process = require("child_process"), var spawn = require("child_process").spawn;
constants = require("constants"), var fs = require('fs');
passwd = require("fs").readFileSync("/etc/passwd", "utf8"),
myUid = process.getuid(), var myUid = process.getuid();
myGid = process.getgid(); var myGid = process.getgid();
if (myUid != 0) {
console.error('must be run as root, otherwise the gid/uid setting will fail.');
process.exit(1);
}
// get a different user. // get a different user.
// don't care who it is, as long as it's not root // don't care who it is, as long as it's not root
var passwd = fs.readFileSync('/etc/passwd', 'utf8');
passwd = passwd.trim().split(/\n/); passwd = passwd.trim().split(/\n/);
for (var i = 0, l = passwd.length; i < l; i ++) { for (var i = 0, l = passwd.length; i < l; i ++) {
if (passwd[i].charAt(0) === "#") continue; if (passwd[i].charAt(0) === "#") continue;
passwd[i] = passwd[i].split(":"); passwd[i] = passwd[i].split(":");
@ -20,22 +27,31 @@ for (var i = 0, l = passwd.length; i < l; i ++) {
break; break;
} }
} }
if (!otherUid && !otherGid) throw new Error("failed getting passwd info."); if (!otherUid && !otherGid) throw new Error('failed getting passwd info.');
console.error("name, id, gid = %j", [otherName, otherUid, otherGid]); console.error('name, id, gid = %j', [otherName, otherUid, otherGid]);
var whoNumber = child_process.spawn("id",[], {uid:otherUid,gid:otherGid}), var whoNumber = spawn('id', [], { uid: otherUid, gid: otherGid });
assert = require("assert"); var whoName = spawn('id', [], { uid: otherName, gid: otherGid });
whoNumber.stdout.buf = "byNumber:"; whoNumber.stdout.buf = 'byNumber:';
whoNumber.stdout.on("data", onData); whoName.stdout.buf = 'byName:';
whoNumber.stdout.on('data', onData);
whoName.stdout.on('data', onData);
function onData (c) { this.buf += c; } function onData (c) { this.buf += c; }
whoNumber.on("exit", onExit); whoNumber.on("exit", onExit);
whoName.on("exit", onExit);
function onExit (code) { function onExit (code) {
var buf = this.stdout.buf; var buf = this.stdout.buf;
console.log(buf); console.log(buf);
var expr = new RegExp("^byNumber:uid="+otherUid+"\\("+ var expr = new RegExp("^(byName|byNumber):uid=" +
otherName+"\\) gid="+otherGid+"\\("); otherUid +
assert.ok(buf.match(expr), "uid and gid should match "+otherName); "\\(" +
otherName +
"\\) gid=" +
otherGid +
"\\(");
assert.ok(buf.match(expr), "uid and gid should match " + otherName);
} }

Loading…
Cancel
Save