diff options
Diffstat (limited to 'src/node_credentials.cc')
-rw-r--r-- | src/node_credentials.cc | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/src/node_credentials.cc b/src/node_credentials.cc new file mode 100644 index 0000000000..0f202dfb75 --- /dev/null +++ b/src/node_credentials.cc @@ -0,0 +1,395 @@ +#include "node_internals.h" + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS +#include <grp.h> // getgrnam() +#include <pwd.h> // getpwnam() +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS + +#if !defined(_MSC_VER) +#include <unistd.h> // setuid, getuid +#endif + +namespace node { + +using v8::Array; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Uint32; +using v8::Value; + +namespace per_process { +bool linux_at_secure = false; +} // namespace per_process + +namespace credentials { + +// Look up environment variable unless running as setuid root. +bool SafeGetenv(const char* key, std::string* text) { +#if !defined(__CloudABI__) && !defined(_WIN32) + if (per_process::linux_at_secure || getuid() != geteuid() || + getgid() != getegid()) + goto fail; +#endif + + { + Mutex::ScopedLock lock(environ_mutex); + if (const char* value = getenv(key)) { + *text = value; + return true; + } + } + +fail: + text->clear(); + return false; +} + +static void SafeGetenv(const FunctionCallbackInfo<Value>& args) { + CHECK(args[0]->IsString()); + Isolate* isolate = args.GetIsolate(); + Utf8Value strenvtag(isolate, args[0]); + std::string text; + if (!SafeGetenv(*strenvtag, &text)) return; + Local<Value> result = + ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked(); + args.GetReturnValue().Set(result); +} + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + +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]; + + errno = 0; + pp = nullptr; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + 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 = nullptr; + + if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->pw_name); + } + + if (rc == 0) errno = ENOENT; + + return nullptr; +} + +static gid_t gid_by_name(const char* name) { + struct group pwd; + struct group* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + 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 = nullptr; + + if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->gr_name); + } + + if (rc == 0) + errno = ENOENT; + + return nullptr; +} +#endif + +static uid_t uid_by_name(Isolate* isolate, Local<Value> value) { + if (value->IsUint32()) { + return static_cast<uid_t>(value.As<Uint32>()->Value()); + } else { + Utf8Value name(isolate, value); + return uid_by_name(*name); + } +} + +static gid_t gid_by_name(Isolate* isolate, Local<Value> value) { + if (value->IsUint32()) { + return static_cast<gid_t>(value.As<Uint32>()->Value()); + } else { + Utf8Value name(isolate, value); + return gid_by_name(*name); + } +} + +static void GetUid(const FunctionCallbackInfo<Value>& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getuid())); +} + +static void GetGid(const FunctionCallbackInfo<Value>& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getgid())); +} + +static void GetEUid(const FunctionCallbackInfo<Value>& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(geteuid())); +} + +static void GetEGid(const FunctionCallbackInfo<Value>& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getegid())); +} + +static void SetGid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setgid(gid)) { + env->ThrowErrnoException(errno, "setgid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetEGid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setegid(gid)) { + env->ThrowErrnoException(errno, "setegid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetUid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setuid(uid)) { + env->ThrowErrnoException(errno, "setuid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetEUid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (seteuid(uid)) { + env->ThrowErrnoException(errno, "seteuid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void GetGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + int ngroups = getgroups(0, nullptr); + + if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups"); + + gid_t* groups = new gid_t[ngroups]; + + ngroups = getgroups(ngroups, groups); + + if (ngroups == -1) { + delete[] groups; + return env->ThrowErrnoException(errno, "getgroups"); + } + + Local<Array> groups_list = Array::New(env->isolate(), ngroups); + bool seen_egid = false; + gid_t egid = getegid(); + + for (int i = 0; i < ngroups; i++) { + groups_list->Set(env->context(), i, Integer::New(env->isolate(), groups[i])) + .FromJust(); + if (groups[i] == egid) seen_egid = true; + } + + delete[] groups; + + if (seen_egid == false) + groups_list + ->Set(env->context(), ngroups, Integer::New(env->isolate(), egid)) + .FromJust(); + + args.GetReturnValue().Set(groups_list); +} + +static void SetGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsArray()); + + 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( + env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked()); + + if (gid == gid_not_found) { + delete[] groups; + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(static_cast<uint32_t>(i + 1)); + return; + } + + groups[i] = gid; + } + + int rc = setgroups(size, groups); + delete[] groups; + + if (rc == -1) return env->ThrowErrnoException(errno, "setgroups"); + + args.GetReturnValue().Set(0); +} + +static void InitGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + CHECK(args[1]->IsUint32() || args[1]->IsString()); + + Utf8Value arg0(env->isolate(), args[0]); + gid_t extra_group; + bool must_free; + char* user; + + if (args[0]->IsUint32()) { + user = name_by_uid(args[0].As<Uint32>()->Value()); + must_free = true; + } else { + user = *arg0; + must_free = false; + } + + if (user == nullptr) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(1); + } + + extra_group = gid_by_name(env->isolate(), args[1]); + + if (extra_group == gid_not_found) { + if (must_free) free(user); + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(2); + } + + int rc = initgroups(user, extra_group); + + if (must_free) free(user); + + if (rc) return env->ThrowErrnoException(errno, "initgroups"); + + args.GetReturnValue().Set(0); +} + +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS + +static void Initialize(Local<Object> target, + Local<Value> unused, + Local<Context> context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + Isolate* isolate = env->isolate(); + + env->SetMethod(target, "safeGetenv", SafeGetenv); + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials"); + env->SetMethodNoSideEffect(target, "getuid", GetUid); + env->SetMethodNoSideEffect(target, "geteuid", GetEUid); + env->SetMethodNoSideEffect(target, "getgid", GetGid); + env->SetMethodNoSideEffect(target, "getegid", GetEGid); + env->SetMethodNoSideEffect(target, "getgroups", GetGroups); + + if (env->is_main_thread()) { + env->SetMethod(target, "initgroups", InitGroups); + env->SetMethod(target, "setegid", SetEGid); + env->SetMethod(target, "seteuid", SetEUid); + env->SetMethod(target, "setgid", SetGid); + env->SetMethod(target, "setuid", SetUid); + env->SetMethod(target, "setgroups", SetGroups); + } +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS +} + +} // namespace credentials +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize) |