From 0a834e648808012a9f55b1d2171f4caf234224ec Mon Sep 17 00:00:00 2001 From: "zodiac@gmail.com" Date: Wed, 29 Jul 2009 18:30:03 +0000 Subject: [PATCH] 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 --- ChangeLog | 15 ++- shellinabox/launcher.c | 232 +++++++++++++++++++++++-------------- shellinabox/service.c | 36 ++++++ shellinabox/shellinaboxd.c | 6 +- 4 files changed, 197 insertions(+), 92 deletions(-) diff --git a/ChangeLog b/ChangeLog index bb06a9a..e1fe74a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2009-07-29 Markus Gutschke + + * Allow unprivileged users to run the daemon. This requires + calling "ssh" instead of "login". + 2009-07-27 Markus Gutschke * 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 diff --git a/shellinabox/launcher.c b/shellinabox/launcher.c index 579086c..1386ae5 100644 --- a/shellinabox/launcher.c +++ b/shellinabox/launcher.c @@ -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, diff --git a/shellinabox/service.c b/shellinabox/service.c index ae6b9ff..d99ca5c 100644 --- a/shellinabox/service.c +++ b/shellinabox/service.c @@ -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; diff --git a/shellinabox/shellinaboxd.c b/shellinabox/shellinaboxd.c index 29bde71..abb2669 100644 --- a/shellinabox/shellinaboxd.c +++ b/shellinabox/shellinaboxd.c @@ -612,7 +612,8 @@ static void usage(void) { "be made available \n" "through the web interface:\n" " SERVICE := ':' APP\n" - " APP := 'LOGIN' | USER ':' CWD ':' \n" + " APP := 'LOGIN' | 'SSH' [ : ] | " + "USER ':' CWD ':' \n" " USER := %s ':' \n" " CWD := 'HOME' | \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);