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:
parent
218d901131
commit
0a834e6488
4 changed files with 197 additions and 92 deletions
15
ChangeLog
15
ChangeLog
|
@ -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>
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue