Mini Shell
/*
* Phusion Passenger - https://www.phusionpassenger.com/
* Copyright (c) 2018 Phusion Holding B.V.
*
* "Passenger", "Phusion Passenger" and "Union Station" are registered
* trademarks of Phusion Holding B.V.
*
* 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.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <limits.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <string>
#include <boost/scoped_array.hpp>
#include <Constants.h>
#include <MainFunctions.h>
#include <ProcessManagement/Utils.h>
#include <Utils/OptionParsing.h>
#include <StrIntTools/StrIntUtils.h>
namespace Passenger {
namespace ExecHelper {
using namespace std;
struct Options {
string user;
int programArgStart;
Options()
: programArgStart(2)
{ }
};
static void
usage() {
// ....|---------------Keep output within standard terminal width (80 chars)------------|
printf("Usage: " AGENT_EXE " exec-helper [OPTIONS...] <PROGRAM> [ARGS...]\n");
printf("Executes the given program under a specific environment.\n");
printf("\n");
printf("Options:\n");
printf(" --user <USER> Execute as the given user. The GID will be set to the\n");
printf(" user's primary group. Supplementary groups will also\n");
printf(" be set.\n");
printf(" --help Show this help message.\n");
}
static bool
parseOption(int argc, const char *argv[], int &i, Options &options) {
OptionParser p(usage);
if (p.isValueFlag(argc, i, argv[i], '\0', "--user")) {
options.user = argv[i + 1];
i += 2;
} else {
return false;
}
return true;
}
static bool
parseOptions(int argc, const char *argv[], Options &options) {
OptionParser p(usage);
int i = 2;
while (i < argc) {
if (parseOption(argc, argv, i, options)) {
continue;
} else if (p.isFlag(argv[i], 'h', "--help")) {
usage();
exit(0);
} else if (*argv[i] == '-') {
fprintf(stderr, "ERROR: unrecognized argument %s. Please type "
"'%s exec-helper --help' for usage.\n", argv[i], argv[0]);
exit(1);
} else {
options.programArgStart = i;
return true;
}
}
return true;
}
static string
describeCommand(int argc, const char *argv[], const Options &options) {
string result = "'";
result.append(argv[options.programArgStart]);
result.append("'");
if (argc > options.programArgStart + 1) {
result.append(" (with params '");
int i = options.programArgStart + 1;
while (i < argc) {
if (i != options.programArgStart + 1) {
result.append(" ");
}
result.append(argv[i]);
i++;
}
result.append("')");
}
return result;
}
static void
reportGetpwuidError(const string &user, int e) {
if (e == 0) {
fprintf(stderr,
"ERROR: Cannot lookup up system user database entry for user '%s':"
" user does not exist\n", user.c_str());
} else {
fprintf(stderr,
"ERROR: Cannot lookup up system user database entry for user '%s':"
" %s (errno=%d)\n",
user.c_str(), strerror(e), e);
}
}
static void
lookupUserGroup(const string &user, uid_t *uid, struct passwd **userInfo, gid_t *gid) {
errno = 0;
*userInfo = getpwnam(user.c_str());
if (*userInfo == NULL) {
if (looksLikePositiveNumber(user)) {
int e = errno;
fprintf(stderr,
"Warning: error looking up system user database"
" entry for user '%s': %s (errno=%d)\n",
user.c_str(), strerror(e), e);
*uid = (uid_t) atoi(user.c_str());
*userInfo = getpwuid(*uid);
if (*userInfo == NULL) {
reportGetpwuidError(user, errno);
exit(1);
} else {
*gid = (*userInfo)->pw_gid;
}
} else {
reportGetpwuidError(user, errno);
exit(1);
}
} else {
*uid = (*userInfo)->pw_uid;
*gid = (*userInfo)->pw_gid;
}
}
static void
switchGroup(uid_t uid, const struct passwd *userInfo, gid_t gid) {
if (userInfo != NULL) {
bool setgroupsCalled = false;
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
#ifdef __APPLE__
int groups[1024];
int ngroups = sizeof(groups) / sizeof(int);
#else
gid_t groups[1024];
int ngroups = sizeof(groups) / sizeof(gid_t);
#endif
boost::scoped_array<gid_t> gidset;
int ret = getgrouplist(userInfo->pw_name, gid,
groups, &ngroups);
if (ret == -1) {
int e = errno;
fprintf(stderr, "ERROR: getgrouplist(%s, %d) failed: %s (errno=%d)\n",
userInfo->pw_name, (int) gid, strerror(e), e);
exit(1);
}
if (ngroups <= NGROUPS_MAX) {
setgroupsCalled = true;
gidset.reset(new gid_t[ngroups]);
for (int i = 0; i < ngroups; i++) {
gidset[i] = groups[i];
}
if (setgroups(ngroups, gidset.get()) == -1) {
int e = errno;
fprintf(stderr, "ERROR: setgroups(%d, ...) failed: %s (errno=%d)\n",
ngroups, strerror(e), e);
exit(1);
}
}
#endif
if (!setgroupsCalled && initgroups(userInfo->pw_name, gid) == -1) {
int e = errno;
fprintf(stderr, "ERROR: initgroups(%s, %d) failed: %s (errno=%d)\n",
userInfo->pw_name, (int) gid, strerror(e), e);
exit(1);
}
}
if (setgid(gid) == -1) {
int e = errno;
fprintf(stderr, "ERROR: setgid(%d) failed: %s (errno=%d)\n",
(int) gid, strerror(e), e);
exit(1);
}
}
static void
switchUser(uid_t uid, const struct passwd *userInfo) {
if (setuid(uid) == -1) {
int e = errno;
fprintf(stderr, "setuid(%d) failed: %s (errno=%d)\n",
(int) uid, strerror(e), e);
exit(1);
}
if (userInfo != NULL) {
setenv("USER", userInfo->pw_name, 1);
setenv("LOGNAME", userInfo->pw_name, 1);
setenv("SHELL", userInfo->pw_shell, 1);
setenv("HOME", userInfo->pw_dir, 1);
} else {
unsetenv("USER");
unsetenv("LOGNAME");
unsetenv("SHELL");
unsetenv("HOME");
}
}
} // namespace ExecHelper
} // namespace Passenger
int
execHelperMain(int argc, char *argv[]) {
using namespace Passenger;
using namespace Passenger::ExecHelper;
if (argc < 3) {
usage();
exit(1);
}
Options options;
if (!parseOptions(argc, const_cast<const char **>(argv), options)) {
fprintf(stderr, "Error parsing arguments.\n");
usage();
exit(1);
}
resetSignalHandlersAndMask();
disableMallocDebugging();
if (!options.user.empty()) {
struct passwd *userInfo;
uid_t uid;
gid_t gid;
lookupUserGroup(options.user, &uid, &userInfo, &gid);
switchGroup(uid, userInfo, gid);
switchUser(uid, userInfo);
}
execvp(argv[options.programArgStart],
(char * const *) &argv[options.programArgStart]);
int e = errno;
fprintf(stderr, "ERROR: unable to execute %s: %s (errno=%d)\n",
describeCommand(argc, const_cast<const char **>(argv), options).c_str(),
strerror(e),
e);
return 1;
}
Zerion Mini Shell 1.0