From 435ece505897c8931548162a1ccc5389e3fc558c Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 10 Jan 2011 18:20:12 -0800 Subject: [PATCH] child_process: Support setting uid/gid by name --- src/node_child_process.cc | 99 +++++++++++++++++++-- src/node_child_process.h | 6 +- test/disabled/test-child-process-uid-gid.js | 46 ++++++---- 3 files changed, 126 insertions(+), 25 deletions(-) diff --git a/src/node_child_process.cc b/src/node_child_process.cc index 9ba7527b2a..6c5c1eb075 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -15,6 +15,8 @@ #include #include #include +#include /* getpwnam() */ +#include /* getgrnam() */ #if defined(__FreeBSD__ ) || defined(__OpenBSD__) #include #endif @@ -26,6 +28,8 @@ extern char **environ; # endif +#include /* PATH_MAX */ + namespace node { using namespace v8; @@ -100,7 +104,11 @@ Handle ChildProcess::Spawn(const Arguments& args) { !args[0]->IsString() || !args[1]->IsArray() || !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."))); } @@ -158,8 +166,33 @@ Handle ChildProcess::Spawn(const Arguments& args) { int fds[3]; - int uid = args[6]->ToInteger()->Value(); - int gid = args[7]->ToInteger()->Value(); + char *custom_uname = NULL; + 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], argv, @@ -168,8 +201,13 @@ Handle ChildProcess::Spawn(const Arguments& args) { fds, custom_fds, do_setsid, - uid, - gid); + custom_uid, + 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]); delete [] argv; @@ -246,7 +284,9 @@ int ChildProcess::Spawn(const char *file, int custom_fds[3], bool do_setsid, int custom_uid, - int custom_gid) { + char *custom_uname, + int custom_gid, + char *custom_gname) { HandleScope scope; assert(pid_ == -1); assert(!ev_is_active(&child_watcher_)); @@ -292,16 +332,59 @@ int ChildProcess::Spawn(const char *file, _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()"); _exit(127); } - if (custom_uid != -1 && setuid(custom_uid)) { + if (uid != -1 && setuid(uid)) { perror("setuid()"); _exit(127); } + if (custom_fds[0] == -1) { close(stdin_pipe[1]); // close write end dup2(stdin_pipe[0], STDIN_FILENO); diff --git a/src/node_child_process.h b/src/node_child_process.h index eec27d5516..05fedb5f08 100644 --- a/src/node_child_process.h +++ b/src/node_child_process.h @@ -65,8 +65,10 @@ class ChildProcess : ObjectWrap { int stdio_fds[3], int custom_fds[3], bool do_setsid, - int uid, - int gid); + int custom_uid, + char *custom_uname, + int custom_gid, + char *custom_gname); // Simple syscall wrapper. Does not disable the watcher. onexit will be // called still. diff --git a/test/disabled/test-child-process-uid-gid.js b/test/disabled/test-child-process-uid-gid.js index 71513c0e0e..b63e2255d2 100644 --- a/test/disabled/test-child-process-uid-gid.js +++ b/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 child_process = require("child_process"), - constants = require("constants"), - passwd = require("fs").readFileSync("/etc/passwd", "utf8"), - myUid = process.getuid(), - myGid = process.getgid(); +var assert = require("assert"); +var spawn = require("child_process").spawn; +var fs = require('fs'); + +var myUid = process.getuid(); +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. // 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/); + for (var i = 0, l = passwd.length; i < l; i ++) { if (passwd[i].charAt(0) === "#") continue; passwd[i] = passwd[i].split(":"); @@ -20,22 +27,31 @@ for (var i = 0, l = passwd.length; i < l; i ++) { 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}), - assert = require("assert"); +var whoNumber = spawn('id', [], { uid: otherUid, gid: otherGid }); +var whoName = spawn('id', [], { uid: otherName, gid: otherGid }); -whoNumber.stdout.buf = "byNumber:"; -whoNumber.stdout.on("data", onData); +whoNumber.stdout.buf = 'byNumber:'; +whoName.stdout.buf = 'byName:'; +whoNumber.stdout.on('data', onData); +whoName.stdout.on('data', onData); function onData (c) { this.buf += c; } whoNumber.on("exit", onExit); +whoName.on("exit", onExit); + function onExit (code) { var buf = this.stdout.buf; console.log(buf); - var expr = new RegExp("^byNumber:uid="+otherUid+"\\("+ - otherName+"\\) gid="+otherGid+"\\("); - assert.ok(buf.match(expr), "uid and gid should match "+otherName); + var expr = new RegExp("^(byName|byNumber):uid=" + + otherUid + + "\\(" + + otherName + + "\\) gid=" + + otherGid + + "\\("); + assert.ok(buf.match(expr), "uid and gid should match " + otherName); }