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>
|
2009-07-27 Markus Gutschke <markus@shellinabox.com>
|
||||||
|
|
||||||
* Use JavaScript redirection for attaching the missing slash to
|
* Use JavaScript redirection for attaching the missing slash to
|
||||||
|
@ -21,11 +26,11 @@
|
||||||
redirecting to a URL that includes a trailing slash.
|
redirecting to a URL that includes a trailing slash.
|
||||||
|
|
||||||
* Run-time testing for availability of libpthread functions does not
|
* Run-time testing for availability of libpthread functions does not
|
||||||
work reliably on some platforms. So, avoid doing so on anything
|
work reliably on some platforms. So, avoid doing so on anything
|
||||||
other than Linux/i386. For all other platforms, assume that the code
|
other than Linux/i386. For all other platforms, assume that the code
|
||||||
is not linked against libpthread. For ShellInABox, this is always
|
is not linked against libpthread. For ShellInABox, this is always
|
||||||
the correct assumption. But if the code gets embedded into other
|
the correct assumption. But if the code gets embedded into other
|
||||||
projects, this might have to be changed.
|
projects, this might have to be changed.
|
||||||
|
|
||||||
2009-07-05 Markus Gutschke <markus@shellinabox.com>
|
2009-07-05 Markus Gutschke <markus@shellinabox.com>
|
||||||
|
|
||||||
|
|
|
@ -159,8 +159,6 @@ static int launcher = -1;
|
||||||
static uid_t restricted;
|
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
|
// 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
|
// conversation function. As we know that this code is only ever called from
|
||||||
// ShellInABox, it can be kept significantly simpler than the more generic
|
// 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;
|
return nc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
|
||||||
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
|
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
|
||||||
static pamc_bp_t *p(pamc_bp_t *p) {
|
static pamc_bp_t *p(pamc_bp_t *p) {
|
||||||
// GCC is too smart for its own good, and triggers a warning in
|
// 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));
|
check(!sigaction(SIGALRM, &sa, NULL));
|
||||||
alarm(60);
|
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;
|
const struct passwd *pw;
|
||||||
pam_handle_t *pam = NULL;
|
pam_handle_t *pam = NULL;
|
||||||
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
if (service->authUser == 2 /* SSH */) {
|
||||||
struct pam_conv conv = { .conv = misc_conv };
|
// Just ask for the user name. SSH will negotiate the password
|
||||||
if (service->authUser) {
|
char *user = NULL;
|
||||||
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;
|
char *prompt;
|
||||||
check(prompt = stringPrintf(NULL, "%s %s", uts.nodename,
|
check(prompt = stringPrintf(NULL, "%s login: ", hostname));
|
||||||
origPrompt ? origPrompt : "login: "));
|
if (read_string(1, prompt, &user) <= 0) {
|
||||||
check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
|
free(user);
|
||||||
|
free(prompt);
|
||||||
// Up to three attempts to enter the user id and password
|
_exit(1);
|
||||||
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);
|
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.
|
// Run SSH as an unprivileged user
|
||||||
const char *name;
|
if ((service->uid = restricted) == 0) {
|
||||||
check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
|
if (runAsUser >= 0) {
|
||||||
pw = getPWEnt(getUserId(name));
|
service->uid = runAsUser;
|
||||||
check(service->uid < 0);
|
} else {
|
||||||
check(service->gid < 0);
|
service->uid = getUserId("nobody");
|
||||||
check(!service->user);
|
}
|
||||||
check(!service->group);
|
if (runAsGroup >= 0) {
|
||||||
service->uid = pw->pw_uid;
|
service->gid = runAsGroup;
|
||||||
service->gid = pw->pw_gid;
|
} else {
|
||||||
check(service->user = strdup(pw->pw_name));
|
service->gid = getGroupId("nogroup");
|
||||||
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);
|
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
|
#else
|
||||||
check(!supportsPAM());
|
check(!supportsPAM());
|
||||||
pw = getPWEnt(service->uid);
|
pw = getPWEnt(service->uid);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if (restricted &&
|
if (restricted &&
|
||||||
(service->uid != restricted || service->gid != pw->pw_gid)) {
|
(service->uid != restricted || service->gid != pw->pw_gid)) {
|
||||||
puts("\nAccess denied!");
|
puts("\nAccess denied!");
|
||||||
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
||||||
pam_end(pam, PAM_SUCCESS);
|
if (service->authUser != 2 /* SSH */) {
|
||||||
|
pam_end(pam, PAM_SUCCESS);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (service->authUser != 2 /* SSH */) {
|
||||||
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
||||||
if (pam) {
|
if (pam) {
|
||||||
#ifdef HAVE_UTMPX_H
|
#ifdef HAVE_UTMPX_H
|
||||||
check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
|
check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
|
||||||
PAM_SUCCESS);
|
PAM_SUCCESS);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
check(!pam);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
check(!pam);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Retrieve supplementary group ids.
|
// Retrieve supplementary group ids.
|
||||||
int ngroups;
|
int ngroups;
|
||||||
|
@ -908,12 +967,15 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
|
||||||
|
|
||||||
// Update utmp/wtmp entries
|
// Update utmp/wtmp entries
|
||||||
#ifdef HAVE_UTMPX_H
|
#ifdef HAVE_UTMPX_H
|
||||||
memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
|
if (service->authUser != 2 /* SSH */) {
|
||||||
strncat(&utmp->utmpx.ut_user[0], service->user, sizeof(utmp->utmpx.ut_user));
|
memset(&utmp->utmpx.ut_user, 0, sizeof(utmp->utmpx.ut_user));
|
||||||
setutxent();
|
strncat(&utmp->utmpx.ut_user[0], service->user,
|
||||||
pututxline(&utmp->utmpx);
|
sizeof(utmp->utmpx.ut_user));
|
||||||
endutxent();
|
setutxent();
|
||||||
updwtmpx("/var/log/wtmp", &utmp->utmpx);
|
pututxline(&utmp->utmpx);
|
||||||
|
endutxent();
|
||||||
|
updwtmpx("/var/log/wtmp", &utmp->utmpx);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
alarm(0);
|
alarm(0);
|
||||||
|
@ -1228,7 +1290,7 @@ static void childProcess(struct Service *service, int width, int height,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, launch the child process.
|
// Finally, launch the child process.
|
||||||
if (service->useLogin) {
|
if (service->useLogin == 1) {
|
||||||
execle("/bin/login", "login", "-p", "-h", peerName,
|
execle("/bin/login", "login", "-p", "-h", peerName,
|
||||||
(void *)0, environment);
|
(void *)0, environment);
|
||||||
execle("/usr/bin/login", "login", "-p", "-h", peerName,
|
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->cwd = strdup("/"));
|
||||||
check(service->cmdline = strdup(
|
check(service->cmdline = strdup(
|
||||||
"/bin/login -p -h ${peer}"));
|
"/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 {
|
} else {
|
||||||
service->useLogin = 0;
|
service->useLogin = 0;
|
||||||
|
|
||||||
|
|
|
@ -612,7 +612,8 @@ static void usage(void) {
|
||||||
"be made available \n"
|
"be made available \n"
|
||||||
"through the web interface:\n"
|
"through the web interface:\n"
|
||||||
" SERVICE := <url-path> ':' APP\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"
|
" USER := %s<username> ':' <groupname>\n"
|
||||||
" CWD := 'HOME' | <dir>\n"
|
" CWD := 'HOME' | <dir>\n"
|
||||||
"\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 the user did not register any services, provide the default service
|
||||||
if (!getHashmapSize(serviceTable)) {
|
if (!getHashmapSize(serviceTable)) {
|
||||||
addToHashMap(serviceTable, "/", (char *)newService(":LOGIN"));
|
addToHashMap(serviceTable, "/", (char *)newService(geteuid() ? ":SSH" :
|
||||||
|
":LOGIN"));
|
||||||
}
|
}
|
||||||
enumerateServices(serviceTable);
|
enumerateServices(serviceTable);
|
||||||
deleteHashMap(serviceTable);
|
deleteHashMap(serviceTable);
|
||||||
|
|
Loading…
Reference in a new issue