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

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,23 +756,78 @@ 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 (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 login: ", hostname));
if (read_string(1, prompt, &user) <= 0) {
free(user);
free(prompt);
_exit(1);
}
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;
// 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);
// 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);
char *prompt;
check(prompt = stringPrintf(NULL, "%s %s", uts.nodename,
check(prompt = stringPrintf(NULL, "%s %s", hostname,
origPrompt ? origPrompt : "login: "));
check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
@ -837,16 +891,20 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
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)
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) {
#ifdef HAVE_UTMPX_H
@ -857,6 +915,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
#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
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));
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);