Allow unprivileged users to run the daemon. This requires calling "ssh" instead of "login".

git-svn-id: https://shellinabox.googlecode.com/svn/trunk@154 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
zodiac@gmail.com 2009-07-29 18:30:03 +00:00
parent 218d901131
commit 0a834e6488
4 changed files with 197 additions and 92 deletions

View file

@ -1,3 +1,8 @@
2009-07-29 Markus Gutschke <markus@shellinabox.com>
* Allow unprivileged users to run the daemon. This requires
calling "ssh" instead of "login".
2009-07-27 Markus Gutschke <markus@shellinabox.com>
* Use JavaScript redirection for attaching the missing slash to
@ -21,11 +26,11 @@
redirecting to a URL that includes a trailing slash.
* Run-time testing for availability of libpthread functions does not
work reliably on some platforms. So, avoid doing so on anything
other than Linux/i386. For all other platforms, assume that the code
is not linked against libpthread. For ShellInABox, this is always
the correct assumption. But if the code gets embedded into other
projects, this might have to be changed.
work reliably on some platforms. So, avoid doing so on anything
other than Linux/i386. For all other platforms, assume that the code
is not linked against libpthread. For ShellInABox, this is always
the correct assumption. But if the code gets embedded into other
projects, this might have to be changed.
2009-07-05 Markus Gutschke <markus@shellinabox.com>

View file

@ -159,8 +159,6 @@ static int launcher = -1;
static uid_t restricted;
#if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
// If the PAM misc library cannot be found, we have to provide our own basic
// conversation function. As we know that this code is only ever called from
// ShellInABox, it can be kept significantly simpler than the more generic
@ -210,6 +208,7 @@ static int read_string(int echo, const char *prompt, char **retstr) {
return nc;
}
#if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
static pamc_bp_t *p(pamc_bp_t *p) {
// GCC is too smart for its own good, and triggers a warning in
@ -757,106 +756,166 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
check(!sigaction(SIGALRM, &sa, NULL));
alarm(60);
// Use PAM to negotiate user authentication and authorization
// Change the prompt to include the host name
const char *hostname = NULL;
if (service->authUser == 2 /* SSH */) {
// If connecting to a remote host, include that hostname
hostname = strrchr(service->cmdline, '@');
if (!hostname || !strcmp(++hostname, "localhost")) {
hostname = NULL;
}
}
struct utsname uts;
memset(&uts, 0, sizeof(uts));
if (!hostname) {
// Find our local hostname
check(!uname(&uts));
hostname = uts.nodename;
}
const struct passwd *pw;
pam_handle_t *pam = NULL;
#if defined(HAVE_SECURITY_PAM_APPL_H)
struct pam_conv conv = { .conv = misc_conv };
if (service->authUser) {
check(supportsPAM());
check(pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
// Change the prompt to include the host name
struct utsname uts;
check(!uname(&uts));
const char *origPrompt;
check(pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
PAM_SUCCESS);
if (service->authUser == 2 /* SSH */) {
// Just ask for the user name. SSH will negotiate the password
char *user = NULL;
char *prompt;
check(prompt = stringPrintf(NULL, "%s %s", uts.nodename,
origPrompt ? origPrompt : "login: "));
check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
// Up to three attempts to enter the user id and password
for (int i = 0;;) {
check(pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
int rc;
if ((rc = pam_authenticate(pam, PAM_SILENT)) ==
PAM_SUCCESS &&
(geteuid() ||
(rc = pam_acct_mgmt(pam, PAM_SILENT)) ==
PAM_SUCCESS)) {
break;
}
if (++i == 3) {
// Quit if login failed.
puts("\nMaximum number of tries exceeded (3)");
pam_end(pam, rc);
_exit(1);
} else {
puts("\nLogin incorrect");
}
check(prompt = stringPrintf(NULL, "%s login: ", hostname));
if (read_string(1, prompt, &user) <= 0) {
free(user);
free(prompt);
_exit(1);
}
check(pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
free(prompt);
char *localhost = strstr(service->cmdline, "@localhost");
if (localhost) {
memcpy(localhost+1, "%s", 3);
}
char *cmdline = stringPrintf(NULL, service->cmdline, user,
hostname);
free(user);
free((void *)service->cmdline);
service->cmdline = cmdline;
// Retrieve user id, and group id.
const char *name;
check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
pw = getPWEnt(getUserId(name));
check(service->uid < 0);
check(service->gid < 0);
check(!service->user);
check(!service->group);
service->uid = pw->pw_uid;
service->gid = pw->pw_gid;
check(service->user = strdup(pw->pw_name));
service->group = getGroupName(pw->pw_gid);
} else {
check(service->uid >= 0);
check(service->gid >= 0);
check(service->user);
check(service->group);
if (supportsPAM()) {
check(pam_start("shellinabox", service->user, &conv, &pam) ==
PAM_SUCCESS);
int rc;
// PAM account management requires root access. Just skip it, if we
// are running with lower privileges.
if (!geteuid() &&
(rc = pam_acct_mgmt(pam, PAM_SILENT)) !=
PAM_SUCCESS) {
pam_end(pam, rc);
_exit(1);
// Run SSH as an unprivileged user
if ((service->uid = restricted) == 0) {
if (runAsUser >= 0) {
service->uid = runAsUser;
} else {
service->uid = getUserId("nobody");
}
if (runAsGroup >= 0) {
service->gid = runAsGroup;
} else {
service->gid = getGroupId("nogroup");
}
}
pw = getPWEnt(service->uid);
}
if (restricted) {
service->gid = pw->pw_gid;
}
service->user = getUserName(service->uid);
service->group = getGroupName(service->gid);
} else {
// Use PAM to negotiate user authentication and authorization
#if defined(HAVE_SECURITY_PAM_APPL_H)
struct pam_conv conv = { .conv = misc_conv };
if (service->authUser) {
check(supportsPAM());
check(pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
const char *origPrompt;
check(pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
PAM_SUCCESS);
char *prompt;
check(prompt = stringPrintf(NULL, "%s %s", hostname,
origPrompt ? origPrompt : "login: "));
check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
// Up to three attempts to enter the user id and password
for (int i = 0;;) {
check(pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
int rc;
if ((rc = pam_authenticate(pam, PAM_SILENT)) ==
PAM_SUCCESS &&
(geteuid() ||
(rc = pam_acct_mgmt(pam, PAM_SILENT)) ==
PAM_SUCCESS)) {
break;
}
if (++i == 3) {
// Quit if login failed.
puts("\nMaximum number of tries exceeded (3)");
pam_end(pam, rc);
_exit(1);
} else {
puts("\nLogin incorrect");
}
}
check(pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
free(prompt);
// Retrieve user id, and group id.
const char *name;
check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
pw = getPWEnt(getUserId(name));
check(service->uid < 0);
check(service->gid < 0);
check(!service->user);
check(!service->group);
service->uid = pw->pw_uid;
service->gid = pw->pw_gid;
check(service->user = strdup(pw->pw_name));
service->group = getGroupName(pw->pw_gid);
} else {
check(service->uid >= 0);
check(service->gid >= 0);
check(service->user);
check(service->group);
if (supportsPAM()) {
check(pam_start("shellinabox", service->user, &conv, &pam) ==
PAM_SUCCESS);
int rc;
// PAM account management requires root access. Just skip it, if we
// are running with lower privileges.
if (!geteuid() &&
(rc = pam_acct_mgmt(pam, PAM_SILENT)) !=
PAM_SUCCESS) {
pam_end(pam, rc);
_exit(1);
}
}
pw = getPWEnt(service->uid);
}
#else
check(!supportsPAM());
pw = getPWEnt(service->uid);
check(!supportsPAM());
pw = getPWEnt(service->uid);
#endif
}
if (restricted &&
(service->uid != restricted || service->gid != pw->pw_gid)) {
puts("\nAccess denied!");
#if defined(HAVE_SECURITY_PAM_APPL_H)
pam_end(pam, PAM_SUCCESS);
if (service->authUser != 2 /* SSH */) {
pam_end(pam, PAM_SUCCESS);
}
#endif
_exit(1);
}
if (service->authUser != 2 /* SSH */) {
#if defined(HAVE_SECURITY_PAM_APPL_H)
if (pam) {
if (pam) {
#ifdef HAVE_UTMPX_H
check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
PAM_SUCCESS);
check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
PAM_SUCCESS);
#endif
}
#else
check(!pam);
#endif
}
#else
check(!pam);
#endif
// Retrieve supplementary group ids.
int ngroups;
@ -908,12 +967,15 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
// Update utmp/wtmp entries
#ifdef HAVE_UTMPX_H
memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
strncat(&utmp->utmpx.ut_user[0], service->user, sizeof(utmp->utmpx.ut_user));
setutxent();
pututxline(&utmp->utmpx);
endutxent();
updwtmpx("/var/log/wtmp", &utmp->utmpx);
if (service->authUser != 2 /* SSH */) {
memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
strncat(&utmp->utmpx.ut_user[0], service->user,
sizeof(utmp->utmpx.ut_user));
setutxent();
pututxline(&utmp->utmpx);
endutxent();
updwtmpx("/var/log/wtmp", &utmp->utmpx);
}
#endif
alarm(0);
@ -1228,7 +1290,7 @@ static void childProcess(struct Service *service, int width, int height,
}
// Finally, launch the child process.
if (service->useLogin) {
if (service->useLogin == 1) {
execle("/bin/login", "login", "-p", "-h", peerName,
(void *)0, environment);
execle("/usr/bin/login", "login", "-p", "-h", peerName,

View file

@ -100,6 +100,42 @@ void initService(struct Service *service, const char *arg) {
check(service->cwd = strdup("/"));
check(service->cmdline = strdup(
"/bin/login -p -h ${peer}"));
} else if (!strcmp(arg, "SSH") || !strncmp(arg, "SSH:", 4)) {
service->useLogin = 0;
service->useHomeDir = 0;
service->authUser = 2;
service->uid = -1;
service->gid = -1;
service->user = NULL;
service->group = NULL;
check(service->cwd = strdup("/"));
char *host;
check(host = strdup("localhost"));
if ((ptr = strchr(arg, ':')) != NULL) {
check(ptr = strdup(ptr + 1));
char *end;
if ((end = strchr(ptr, ':')) != NULL) {
*end = '\000';
}
if (*ptr) {
free(host);
host = ptr;
} else {
free(ptr);
}
}
service->cmdline = stringPrintf(NULL,
"ssh -a -e none -i /dev/null -x -oChallengeResponseAuthentication=no "
"-oCheckHostIP=no -oClearAllForwardings=yes -oCompression=no "
"-oControlMaster=no -oGSSAPIAuthentication=no "
"-oHostbasedAuthentication=no -oIdentitiesOnly=yes "
"-oKbdInteractiveAuthentication=yes -oPasswordAuthentication=yes "
"-oPreferredAuthentications=keyboard-interactive,password "
"-oPubkeyAuthentication=no -oRhostsRSAAuthentication=no "
"-oRSAAuthentication=no -oStrictHostKeyChecking=no -oTunnel=no "
"-oUserKnownHostsFile=/dev/null -oVerifyHostKeyDNS=no "
"-oVisualHostKey=no -oLogLevel=QUIET %%s@%s", host);
free(host);
} else {
service->useLogin = 0;

View file

@ -612,7 +612,8 @@ static void usage(void) {
"be made available \n"
"through the web interface:\n"
" SERVICE := <url-path> ':' APP\n"
" APP := 'LOGIN' | USER ':' CWD ':' <cmdline>\n"
" APP := 'LOGIN' | 'SSH' [ : <host> ] | "
"USER ':' CWD ':' <cmdline>\n"
" USER := %s<username> ':' <groupname>\n"
" CWD := 'HOME' | <dir>\n"
"\n"
@ -880,7 +881,8 @@ static void parseArgs(int argc, char * const argv[]) {
// If the user did not register any services, provide the default service
if (!getHashmapSize(serviceTable)) {
addToHashMap(serviceTable, "/", (char *)newService(":LOGIN"));
addToHashMap(serviceTable, "/", (char *)newService(geteuid() ? ":SSH" :
":LOGIN"));
}
enumerateServices(serviceTable);
deleteHashMap(serviceTable);