- Tightened setuid operation to not allow any user or group changes.
- Added support for --cgi mode. git-svn-id: https://shellinabox.googlecode.com/svn/trunk@22 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
parent
8f2d101f6d
commit
35aa1a7881
19 changed files with 468 additions and 84 deletions
|
@ -62,6 +62,7 @@ shellinaboxd_SOURCES = shellinabox/shellinaboxd.c \
|
|||
shellinabox/service.h \
|
||||
shellinabox/session.c \
|
||||
shellinabox/session.h \
|
||||
shellinabox/cgi_root.html \
|
||||
shellinabox/root_page.html \
|
||||
shellinabox/vt100.js \
|
||||
shellinabox/shell_in_a_box.js \
|
||||
|
|
|
@ -73,7 +73,8 @@ am__dirstamp = $(am__leading_dot)dirstamp
|
|||
am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \
|
||||
externalfile.$(OBJEXT) launcher.$(OBJEXT) privileges.$(OBJEXT) \
|
||||
service.$(OBJEXT) session.$(OBJEXT) \
|
||||
shellinabox/root_page.$(OBJEXT) shellinabox/vt100.$(OBJEXT) \
|
||||
shellinabox/cgi_root.$(OBJEXT) shellinabox/root_page.$(OBJEXT) \
|
||||
shellinabox/vt100.$(OBJEXT) \
|
||||
shellinabox/shell_in_a_box.$(OBJEXT) \
|
||||
shellinabox/styles.$(OBJEXT) shellinabox/favicon.$(OBJEXT) \
|
||||
shellinabox/beep.$(OBJEXT)
|
||||
|
@ -299,6 +300,7 @@ shellinaboxd_SOURCES = shellinabox/shellinaboxd.c \
|
|||
shellinabox/service.h \
|
||||
shellinabox/session.c \
|
||||
shellinabox/session.h \
|
||||
shellinabox/cgi_root.html \
|
||||
shellinabox/root_page.html \
|
||||
shellinabox/vt100.js \
|
||||
shellinabox/shell_in_a_box.js \
|
||||
|
@ -394,6 +396,8 @@ shellinabox/$(am__dirstamp):
|
|||
shellinabox/$(DEPDIR)/$(am__dirstamp):
|
||||
@$(MKDIR_P) shellinabox/$(DEPDIR)
|
||||
@: > shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||
shellinabox/cgi_root.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||
shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||
shellinabox/root_page.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||
shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||
shellinabox/vt100.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||
|
@ -413,6 +417,7 @@ shellinaboxd$(EXEEXT): $(shellinaboxd_OBJECTS) $(shellinaboxd_DEPENDENCIES)
|
|||
mostlyclean-compile:
|
||||
-rm -f *.$(OBJEXT)
|
||||
-rm -f shellinabox/beep.$(OBJEXT)
|
||||
-rm -f shellinabox/cgi_root.$(OBJEXT)
|
||||
-rm -f shellinabox/favicon.$(OBJEXT)
|
||||
-rm -f shellinabox/root_page.$(OBJEXT)
|
||||
-rm -f shellinabox/shell_in_a_box.$(OBJEXT)
|
||||
|
|
|
@ -64,8 +64,11 @@ typedef struct ServerConnection ServerConnection;
|
|||
typedef struct Server Server;
|
||||
typedef struct URL URL;
|
||||
|
||||
Server *newCGIServer(int portMin, int portMax, int timeout);
|
||||
Server *newServer(int port);
|
||||
void deleteServer(Server *server);
|
||||
int serverGetListeningPort(Server *server);
|
||||
int serverGetFd(Server *server);
|
||||
void serverRegisterHttpHandler(Server *server, const char *url,
|
||||
int (*handler)(HttpConnection *, void *,
|
||||
const char *, int), void *arg);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
newCGIServer
|
||||
newServer
|
||||
deleteServer
|
||||
serverGetListeningPort
|
||||
serverGetFd
|
||||
serverRegisterHttpHandler
|
||||
serverRegisterStreamingHttpHandler
|
||||
serverAddConnection
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <string.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -166,6 +167,58 @@ static int serverQuitHandler(struct HttpConnection *http, void *arg) {
|
|||
return HTTP_DONE;
|
||||
}
|
||||
|
||||
struct Server *newCGIServer(int portMin, int portMax, int timeout) {
|
||||
struct Server *server = newServer(0);
|
||||
server->serverTimeout = timeout;
|
||||
int true = 1;
|
||||
server->serverFd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
check(server->serverFd >= 0);
|
||||
check(!setsockopt(server->serverFd, SOL_SOCKET, SO_REUSEADDR,
|
||||
&true, sizeof(true)));
|
||||
struct sockaddr_in serverAddr = { 0 };
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
// Linux unlike BSD does not have support for picking a local port range.
|
||||
// So, we have to randomly pick a port from our allowed port range, and then
|
||||
// keep iterating until we find an unused port.
|
||||
if (portMin || portMax) {
|
||||
struct timeval tv;
|
||||
check(!gettimeofday(&tv, NULL));
|
||||
srand((int)(tv.tv_usec ^ tv.tv_sec));
|
||||
check(portMin > 0);
|
||||
check(portMax < 65536);
|
||||
check(portMax >= portMin);
|
||||
int portStart = rand() % (portMax - portMin + 1) + portMin;
|
||||
for (int p = 0; p <= portMax-portMin; p++) {
|
||||
int port = (p+portStart)%(portMax-portMin+1)+ portMin;
|
||||
serverAddr.sin_port = htons(port);
|
||||
if (!bind(server->serverFd, (struct sockaddr *)&serverAddr,
|
||||
sizeof(serverAddr))) {
|
||||
break;
|
||||
}
|
||||
serverAddr.sin_port = 0;
|
||||
}
|
||||
if (!serverAddr.sin_port) {
|
||||
fatal("Failed to find any available port");
|
||||
}
|
||||
}
|
||||
|
||||
check(!listen(server->serverFd, SOMAXCONN));
|
||||
socklen_t socklen = (socklen_t)sizeof(serverAddr);
|
||||
check(!getsockname(server->serverFd, (struct sockaddr *)&serverAddr,
|
||||
&socklen));
|
||||
check(socklen == sizeof(serverAddr));
|
||||
server->port = ntohs(serverAddr.sin_port);
|
||||
info("Listening on port %d", server->port);
|
||||
|
||||
check(server->pollFds = malloc(sizeof(struct pollfd)));
|
||||
server->pollFds->fd = server->serverFd;
|
||||
server->pollFds->events = POLLIN;
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
struct Server *newServer(int port) {
|
||||
struct Server *server;
|
||||
check(server = malloc(sizeof(struct Server)));
|
||||
|
@ -177,6 +230,7 @@ void initServer(struct Server *server, int port) {
|
|||
server->port = port;
|
||||
server->looping = 0;
|
||||
server->exitAll = 0;
|
||||
server->serverTimeout = -1;
|
||||
server->serverFd = -1;
|
||||
server->numericHosts = 0;
|
||||
server->pollFds = NULL;
|
||||
|
@ -208,6 +262,14 @@ void deleteServer(struct Server *server) {
|
|||
free(server);
|
||||
}
|
||||
|
||||
int serverGetListeningPort(struct Server *server) {
|
||||
return server->port;
|
||||
}
|
||||
|
||||
int serverGetFd(struct Server *server) {
|
||||
return server->serverFd;
|
||||
}
|
||||
|
||||
struct ServerConnection *serverAddConnection(struct Server *server, int fd,
|
||||
int (*handleConnection)(struct ServerConnection *c,
|
||||
void *arg, short *events,
|
||||
|
@ -363,6 +425,14 @@ void serverLoop(struct Server *server) {
|
|||
}
|
||||
}
|
||||
|
||||
// serverTimeout is always a delta value, unlike connection timeouts
|
||||
// which are absolute times.
|
||||
if (server->serverTimeout >= 0) {
|
||||
if (timeout < 0 || timeout > server->serverTimeout + currentTime) {
|
||||
timeout = server->serverTimeout+currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout >= 0) {
|
||||
// Wait at least one second longer than needed, so that even if
|
||||
// poll() decides to return a second early (due to possible rounding
|
||||
|
@ -406,6 +476,12 @@ void serverLoop(struct Server *server) {
|
|||
INITIAL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (server->serverTimeout > 0 && !server->numConnections) {
|
||||
// In CGI mode, exit the server, if we haven't had any active
|
||||
// connections in a while.
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 1;
|
||||
(isTimeout || eventCount > 0) && i <= server->numConnections;
|
||||
|
|
|
@ -68,6 +68,7 @@ struct Server {
|
|||
int port;
|
||||
int looping;
|
||||
int exitAll;
|
||||
int serverTimeout;
|
||||
int serverFd;
|
||||
int numericHosts;
|
||||
struct pollfd *pollFds;
|
||||
|
@ -77,10 +78,13 @@ struct Server {
|
|||
struct SSLSupport ssl;
|
||||
};
|
||||
|
||||
struct Server *newCGIServer(int portMin, int portMax, int timeout);
|
||||
struct Server *newServer(int port);
|
||||
void initServer(struct Server *server, int port);
|
||||
void destroyServer(struct Server *server);
|
||||
void deleteServer(struct Server *server);
|
||||
int serverGetListeningPort(struct Server *server);
|
||||
int serverGetFd(struct Server *server);
|
||||
void serverRegisterHttpHandler(struct Server *server, const char *url,
|
||||
int (*handler)(struct HttpConnection *, void *,
|
||||
const char *, int), void *arg);
|
||||
|
|
|
@ -74,7 +74,8 @@ void info(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
|||
void warn(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void error(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void message(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void fatal(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void fatal(const char *fmt, ...) __attribute__((format(printf, 1, 2),
|
||||
noreturn));
|
||||
int logIsDebug(void);
|
||||
int logIsInfo(void);
|
||||
int logIsWarn(void);
|
||||
|
|
69
shellinabox/cgi_root.html
Normal file
69
shellinabox/cgi_root.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<!--
|
||||
ShellInABox - Make command line applications available as AJAX web applications
|
||||
Copyright (C) 2008 Markus Gutschke markus@shellinabox.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
In addition to these license terms, the author grants the following
|
||||
additional rights:
|
||||
|
||||
If you modify this program, or any covered work, by linking or
|
||||
combining it with the OpenSSL project's OpenSSL library (or a
|
||||
modified version of that library), containing parts covered by the
|
||||
terms of the OpenSSL or SSLeay licenses, the author
|
||||
grants you additional permission to convey the resulting work.
|
||||
Corresponding Source for a non-source form of such a combination
|
||||
shall include the source code for the parts of OpenSSL used as well
|
||||
as that of the covered work.
|
||||
|
||||
You may at your option choose to remove this additional permission from
|
||||
the work, or from any part of it.
|
||||
|
||||
It is possible to build this program in a way that it loads OpenSSL
|
||||
libraries at run-time. If doing so, the following notices are required
|
||||
by the OpenSSL and SSLeay licenses:
|
||||
|
||||
This product includes software developed by the OpenSSL Project
|
||||
for use in the OpenSSL Toolkit. (http://www.openssl.org/)
|
||||
|
||||
This product includes cryptographic software written by Eric Young
|
||||
(eay@cryptsoft.com)
|
||||
|
||||
The most up-to-date version of this program is always available from
|
||||
http://shellinabox.com
|
||||
-->
|
||||
<title>Shell In A Box</title>
|
||||
<script type="text/javascript"><!--
|
||||
(function() {
|
||||
var url = document.location.protocol + '//' +
|
||||
document.location.hostname + ':%d/';
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'shortcut icon';
|
||||
link.type = 'image/x-icon';
|
||||
link.href = url + 'favicon.ico';
|
||||
head.appendChild(link);
|
||||
document.write('<frameset cols="*">\n' +
|
||||
'<frame src="' + url +
|
||||
document.location.search.replace(/^\?/, '') + '#' +
|
||||
encodeURIComponent(document.location.href) + '">\n' +
|
||||
'</frameset>');
|
||||
})();
|
||||
--></script>
|
||||
</head>
|
||||
</html>
|
|
@ -103,6 +103,7 @@ static int (*x_misc_conv)(int, const struct pam_message **,
|
|||
extern DIR *fdopendir(int) __attribute__((weak));
|
||||
|
||||
static int launcher = -1;
|
||||
static uid_t restricted;
|
||||
|
||||
|
||||
static void *loadSymbol(const char *lib, const char *fn) {
|
||||
|
@ -288,6 +289,60 @@ static void destroyUtmpHashEntry(void *arg, char *key, char *value) {
|
|||
deleteUtmp((struct Utmp *)value);
|
||||
}
|
||||
|
||||
void closeAllFds(int *exceptFds, int num) {
|
||||
// Close all file handles. If possible, scan through "/proc/self/fd" as
|
||||
// that is faster than calling close() on all possible file handles.
|
||||
int nullFd = open("/dev/null", O_RDWR);
|
||||
int dirFd = !&fdopendir ? -1 : open("/proc/self/fd", O_RDONLY);
|
||||
if (dirFd < 0) {
|
||||
for (int i = sysconf(_SC_OPEN_MAX); --i > 0; ) {
|
||||
if (i != nullFd) {
|
||||
for (int j = 0; j < num; j++) {
|
||||
if (i == exceptFds[j]) {
|
||||
goto no_close_1;
|
||||
}
|
||||
}
|
||||
// Closing handles 0..2 is never a good idea. Instead, redirect them
|
||||
// to /dev/null
|
||||
if (i <= 2) {
|
||||
NOINTR(dup2(nullFd, i));
|
||||
} else {
|
||||
NOINTR(close(i));
|
||||
}
|
||||
}
|
||||
no_close_1:;
|
||||
}
|
||||
} else {
|
||||
DIR *dir;
|
||||
check(dir = fdopendir(dirFd));
|
||||
struct dirent de, *res;
|
||||
while (!readdir_r(dir, &de, &res) && res) {
|
||||
if (res->d_name[0] < '0')
|
||||
continue;
|
||||
int fd = atoi(res->d_name);
|
||||
if (fd != nullFd && fd != dirFd) {
|
||||
for (int j = 0; j < num; j++) {
|
||||
if (fd == exceptFds[j]) {
|
||||
goto no_close_2;
|
||||
}
|
||||
}
|
||||
// Closing handles 0..2 is never a good idea. Instead, redirect them
|
||||
// to /dev/null
|
||||
if (fd <= 2) {
|
||||
NOINTR(dup2(nullFd, fd));
|
||||
} else {
|
||||
NOINTR(close(fd));
|
||||
}
|
||||
}
|
||||
no_close_2:;
|
||||
}
|
||||
check(!closedir(dir));
|
||||
}
|
||||
if (nullFd > 2) {
|
||||
check(!close(nullFd));
|
||||
}
|
||||
}
|
||||
|
||||
static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
|
||||
const char *peerName) {
|
||||
int slave;
|
||||
|
@ -323,27 +378,7 @@ static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
|
|||
(*utmp)->utmpx.ut_pid = pid;
|
||||
(*utmp)->pty = slave;
|
||||
|
||||
// Close all file handles. If possible, scan through "/proc/self/fd" as
|
||||
// that is faster than calling close() on all possible file handles.
|
||||
int dirFd = !&fdopendir ? -1 : open("/proc/self/fd", O_RDONLY);
|
||||
if (dirFd < 0) {
|
||||
for (int i = sysconf(_SC_OPEN_MAX); --i > 0; ) {
|
||||
if (i != slave) {
|
||||
NOINTR(close(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DIR *dir;
|
||||
check(dir = fdopendir(dirFd));
|
||||
struct dirent de, *res;
|
||||
while (!readdir_r(dir, &de, &res) && res) {
|
||||
int fd = atoi(res->d_name);
|
||||
if (fd != slave && fd != dirFd) {
|
||||
NOINTR(close(fd));
|
||||
}
|
||||
}
|
||||
check(!closedir(dir));
|
||||
}
|
||||
closeAllFds((int []){ slave }, 1);
|
||||
|
||||
// Become the session/process-group leader
|
||||
setsid();
|
||||
|
@ -958,7 +993,7 @@ static void launcherDaemon(int fd) {
|
|||
_exit(0);
|
||||
}
|
||||
|
||||
void forkLauncher(void) {
|
||||
int forkLauncher(void) {
|
||||
int pair[2];
|
||||
check(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
|
||||
|
||||
|
@ -973,14 +1008,14 @@ void forkLauncher(void) {
|
|||
// switch back to root, which is necessary for launching "login".
|
||||
lowerPrivileges();
|
||||
NOINTR(close(pair[0]));
|
||||
closeAllFds((int []){ pair[1] }, 1);
|
||||
launcherDaemon(pair[1]);
|
||||
fatal("exit() failed!");
|
||||
case -1:
|
||||
fatal("fork() failed!");
|
||||
break;
|
||||
default:
|
||||
NOINTR(close(pair[1]));
|
||||
launcher = pair[0];
|
||||
return;
|
||||
return launcher;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ struct LaunchRequest {
|
|||
int supportsPAM(void);
|
||||
int launchChild(int service, struct Session *session);
|
||||
void setWindowSize(int pty, int width, int height);
|
||||
void forkLauncher(void);
|
||||
int forkLauncher(void);
|
||||
void closeAllFds(int *exceptFd, int num);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -60,40 +60,48 @@
|
|||
|
||||
int runAsUser = -1;
|
||||
int runAsGroup = -1;
|
||||
uid_t restricted;
|
||||
|
||||
|
||||
void removeGroupPrivileges(void) {
|
||||
static void removeGroupPrivileges(int showError) {
|
||||
gid_t rg, eg, sg;
|
||||
check(!getresgid(&rg, &eg, &sg));
|
||||
|
||||
// Remove all supplementary groups. Allow this command to fail. That could
|
||||
// happen if we run as an unprivileged user.
|
||||
setgroups(0, (gid_t *)"");
|
||||
|
||||
if (runAsGroup >= 0) {
|
||||
uid_t ru, eu, su;
|
||||
getresuid(&ru, &eu, &su);
|
||||
|
||||
// Try to switch the user-provided group.
|
||||
if (setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
||||
if (restricted) {
|
||||
_exit(1);
|
||||
} else {
|
||||
if ((ru && runAsGroup != rg) ||
|
||||
setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
||||
if (showError) {
|
||||
fatal("Only privileged users can change their group memberships");
|
||||
} else {
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gid_t r, e, s;
|
||||
check(!getresgid(&r, &e, &s));
|
||||
if (r) {
|
||||
if (rg) {
|
||||
// If we were started as a set-gid binary, drop these permissions, now.
|
||||
check(!setresgid(r, r, r));
|
||||
check(!setresgid(rg, rg, rg));
|
||||
} else {
|
||||
// If we are running as root, switch to "nogroup"
|
||||
gid_t n = getGroupId("nogroup");
|
||||
check(!setresgid(n, n, n));
|
||||
gid_t ng = getGroupId("nogroup");
|
||||
check(!setresgid(ng, ng, ng));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void lowerPrivileges(void) {
|
||||
uid_t r, e, g;
|
||||
check(!getresuid(&r, &e, &g));
|
||||
|
||||
// Permanently lower all group permissions. We do not actually need these,
|
||||
// as we still have "root" user privileges in our saved-uid.
|
||||
removeGroupPrivileges();
|
||||
removeGroupPrivileges(0);
|
||||
|
||||
// Temporarily lower user privileges. If we used to have "root" privileges,
|
||||
// we can later still regain them.
|
||||
|
@ -101,10 +109,11 @@ void lowerPrivileges(void) {
|
|||
|
||||
if (runAsUser >= 0) {
|
||||
// Try to switch to the user-provided user id.
|
||||
if (r && runAsUser != r) {
|
||||
fatal("Only privileged users can change their user id");
|
||||
}
|
||||
check(!setresuid(runAsUser, runAsUser, -1));
|
||||
} else {
|
||||
uid_t r, e, s;
|
||||
check(!getresuid(&r, &e, &s));
|
||||
if (r) {
|
||||
// If we were started as a set-uid binary, temporarily lower these
|
||||
// permissions.
|
||||
|
@ -118,17 +127,19 @@ void lowerPrivileges(void) {
|
|||
}
|
||||
|
||||
void dropPrivileges(void) {
|
||||
uid_t r, e, s;
|
||||
check(!getresuid(&r, &e, &s));
|
||||
|
||||
// Drop all group privileges.
|
||||
removeGroupPrivileges();
|
||||
removeGroupPrivileges(1);
|
||||
|
||||
if (runAsUser >= 0) {
|
||||
// Try to switch to the user-provided user id.
|
||||
if (setresuid(runAsUser, runAsUser, runAsUser)) {
|
||||
if ((r && runAsUser != r) ||
|
||||
setresuid(runAsUser, runAsUser, runAsUser)) {
|
||||
fatal("Only privileged users can change their user id.");
|
||||
}
|
||||
} else {
|
||||
uid_t r, e, s;
|
||||
check(!getresuid(&r, &e, &s));
|
||||
if (r) {
|
||||
// If we were started as a set-uid binary, permanently drop these
|
||||
// permissions.
|
||||
|
|
|
@ -50,9 +50,7 @@
|
|||
|
||||
extern int runAsUser;
|
||||
extern int runAsGroup;
|
||||
extern uid_t restricted;
|
||||
|
||||
void removeGroupPrivileges(void);
|
||||
void lowerPrivileges(void);
|
||||
void dropPrivileges(void);
|
||||
const char *getUserName(uid_t uid);
|
||||
|
|
|
@ -68,6 +68,11 @@
|
|||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
<script type="text/javascript" src="ShellInABox.js"></script>
|
||||
</head>
|
||||
<body onload="new ShellInABox()" scroll="no"><noscript>JavaScript
|
||||
<!-- Load ShellInABox from a timer as Konqueror sometimes fails to
|
||||
correctly deal with the enclosing frameset (if any), if we do not
|
||||
do this
|
||||
-->
|
||||
<body onload="setTimeout('new ShellInABox()', 100)"
|
||||
scroll="no"><noscript>JavaScript
|
||||
must be enabled for ShellInABox</noscript></body>
|
||||
</html>
|
||||
|
|
|
@ -218,3 +218,7 @@ struct Session *findSession(int *isNew, HttpConnection *http, URL *url) {
|
|||
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){
|
||||
iterateOverHashMap(sessions, fnc, arg);
|
||||
}
|
||||
|
||||
int numSessions(void) {
|
||||
return getHashmapSize(sessions);
|
||||
}
|
||||
|
|
|
@ -77,5 +77,6 @@ void finishSession(struct Session *session);
|
|||
void finishAllSessions(void);
|
||||
struct Session *findSession(int *isNew, HttpConnection *http, URL *url);
|
||||
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg);
|
||||
int numSessions(void);
|
||||
|
||||
#endif /* SESSION_H__ */
|
||||
|
|
|
@ -91,11 +91,16 @@ function extend(subClass, baseClass) {
|
|||
|
||||
function ShellInABox(url, container) {
|
||||
if (url == undefined) {
|
||||
this.url = document.location.href;
|
||||
this.url = document.location.href.replace(/[?#].*/, '');
|
||||
} else {
|
||||
this.url = url;
|
||||
}
|
||||
if (document.location.hash != '') {
|
||||
this.nextUrl = decodeURIComponent(document.location.hash).
|
||||
replace(/^#/, '');
|
||||
} else {
|
||||
this.nextUrl = this.url;
|
||||
}
|
||||
this.session = null;
|
||||
this.pendingKeys = '';
|
||||
this.keysInFlight = false;
|
||||
|
@ -125,6 +130,13 @@ ShellInABox.prototype.sessionClosed = function() {
|
|||
ShellInABox.prototype.reconnect = function() {
|
||||
this.showReconnect(false);
|
||||
if (!this.session) {
|
||||
if (document.location.hash != '') {
|
||||
// A shellinaboxd daemon launched from a CGI only allows a single
|
||||
// session. In order to reconnect, we must reload the frame definition
|
||||
// and obtain a new port number. As this is a different origin, we
|
||||
// need to get enclosing page to help us.
|
||||
parent.location = this.nextUrl;
|
||||
} else {
|
||||
if (this.url != this.nextUrl) {
|
||||
document.location.replace(this.nextUrl);
|
||||
} else {
|
||||
|
@ -134,6 +146,8 @@ ShellInABox.prototype.reconnect = function() {
|
|||
this.sendRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
ShellInABox.prototype.sendRequest = function(request) {
|
||||
|
@ -176,6 +190,7 @@ ShellInABox.prototype.onReadyStateChange = function(request) {
|
|||
}
|
||||
} else if (request.status == 0) {
|
||||
// Time Out
|
||||
this.inspect(request);/***/
|
||||
this.sendRequest(request);
|
||||
} else {
|
||||
this.sessionClosed();
|
||||
|
@ -262,9 +277,20 @@ ShellInABox.prototype.resized = function(w, h) {
|
|||
};
|
||||
|
||||
ShellInABox.prototype.toggleSSL = function() {
|
||||
if (document.location.hash != '') {
|
||||
if (this.nextUrl.match(/\?plain$/)) {
|
||||
this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
|
||||
} else {
|
||||
this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
|
||||
}
|
||||
if (!this.session) {
|
||||
parent.location = this.nextUrl;
|
||||
}
|
||||
} else {
|
||||
this.nextUrl = this.nextUrl.match(/^https:/)
|
||||
? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
|
||||
: this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
|
||||
}
|
||||
if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
|
||||
this.nextUrl += '/';
|
||||
}
|
||||
|
@ -290,8 +316,13 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
|
|||
// If the server supports both SSL and plain text connections,
|
||||
// provide a menu entry to switch between the two.
|
||||
var newNode = document.createElement('li');
|
||||
newNode.innerHTML =
|
||||
(this.nextUrl.match(/^https:/) ? '✔ ' : '') + 'Secure';
|
||||
var isSecure;
|
||||
if (document.location.href != '') {
|
||||
isSecure = !this.nextUrl.match(/\?plain$/);
|
||||
} else {
|
||||
isSecure = this.nextUrl.match(/^https:/);
|
||||
}
|
||||
newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
|
||||
if (node.nextSibling) {
|
||||
entries.insertBefore(newNode, node.nextSibling);
|
||||
} else {
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
@ -70,11 +71,14 @@
|
|||
#define PORTNUM 4200
|
||||
#define MAX_RESPONSE 2048
|
||||
|
||||
static int port = PORTNUM;
|
||||
static int port;
|
||||
static int portMin;
|
||||
static int portMax;
|
||||
static int numericHosts = 0;
|
||||
static int enableSSL = 1;
|
||||
static char *certificateDir;
|
||||
static HashMap *externalFiles;
|
||||
static Server *cgiServer;
|
||||
|
||||
|
||||
static char *jsonEscape(const char *buf, int len) {
|
||||
|
@ -288,6 +292,10 @@ static int dataHandler(HttpConnection *http, struct Service *service,
|
|||
int isNew;
|
||||
struct Session *session = findSession(&isNew, http, url);
|
||||
if (session == NULL) {
|
||||
if (cgiServer && numSessions() == 0) {
|
||||
// CGI servers only ever serve a single session
|
||||
serverExitLoop(cgiServer, 1);
|
||||
}
|
||||
httpSendReply(http, 400, "Bad Request", NULL);
|
||||
return HTTP_DONE;
|
||||
}
|
||||
|
@ -324,6 +332,11 @@ static int dataHandler(HttpConnection *http, struct Service *service,
|
|||
|
||||
// Create a new session, if the client did not provide an existing one
|
||||
if (isNew) {
|
||||
if (cgiServer && numSessions() > 1) {
|
||||
deleteSession(session);
|
||||
httpSendReply(http, 400, "Bad Request", NULL);
|
||||
return HTTP_DONE;
|
||||
}
|
||||
debug("Creating new child process");
|
||||
if (launchChild(service->id, session) < 0) {
|
||||
deleteSession(session);
|
||||
|
@ -540,6 +553,7 @@ static void usage(void) {
|
|||
"List of command line options:\n"
|
||||
" -b, --background[=PIDFILE] run in background\n"
|
||||
"%s"
|
||||
" --cgi[=PORTMIN-PORTMAX] run as CGI\n"
|
||||
" -d, --debug enable debug mode\n"
|
||||
" -f, --static-file=URL:FILE serve static file from URL path\n"
|
||||
" -g, --group=GID switch to this group (default: %s)\n"
|
||||
|
@ -594,16 +608,18 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
enableSSL = 0;
|
||||
}
|
||||
int demonize = 0;
|
||||
int cgi = 0;
|
||||
const char *pidfile = NULL;
|
||||
int verbosity = MSG_DEFAULT;
|
||||
externalFiles = newHashMap(destroyExternalFileHashEntry, NULL);
|
||||
HashMap *services = newHashMap(destroyServiceHashEntry, NULL);
|
||||
HashMap *serviceTable = newHashMap(destroyServiceHashEntry, NULL);
|
||||
for (;;) {
|
||||
static const char optstring[] = "+hbc:df:g:np:s:tqu:v";
|
||||
static const char optstring[] = "+hb::c:df:g:np:s:tqu:v";
|
||||
static struct option options[] = {
|
||||
{ "help", 0, 0, 'h' },
|
||||
{ "background", 2, 0, 'b' },
|
||||
{ "cert", 1, 0, 'c' },
|
||||
{ "cgi", 2, 0, 0 },
|
||||
{ "debug", 0, 0, 'd' },
|
||||
{ "static-file", 1, 0, 'f' },
|
||||
{ "group", 1, 0, 'g' },
|
||||
|
@ -634,6 +650,9 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
exit(idx != -1);
|
||||
} else if (!idx--) {
|
||||
// Background
|
||||
if (cgi) {
|
||||
fatal("CGI and background operations are mutually exclusive");
|
||||
}
|
||||
demonize = 1;
|
||||
if (optarg && pidfile) {
|
||||
fatal("Only one pidfile can be given");
|
||||
|
@ -650,6 +669,25 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
fatal("Only one certificate directory can be selected");
|
||||
}
|
||||
check(certificateDir = strdup(optarg));
|
||||
} else if (!idx--) {
|
||||
// CGI
|
||||
if (demonize) {
|
||||
fatal("CGI and background operations are mutually exclusive");
|
||||
}
|
||||
if (port) {
|
||||
fatal("Cannot specify a port for CGI operation");
|
||||
}
|
||||
cgi = 1;
|
||||
if (optarg) {
|
||||
char *ptr = strchr(optarg, '-');
|
||||
if (!ptr) {
|
||||
fatal("Syntax error in port range specification");
|
||||
}
|
||||
*ptr = '\000';
|
||||
portMin = strtoint(optarg, 1, 65535);
|
||||
*ptr = '-';
|
||||
portMax = strtoint(ptr + 1, portMin, 65535);
|
||||
}
|
||||
} else if (!idx--) {
|
||||
// Debug
|
||||
if (!logIsDefault() && !logIsDebug()) {
|
||||
|
@ -682,15 +720,21 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
numericHosts = 1;
|
||||
} else if (!idx--) {
|
||||
// Port
|
||||
if (port) {
|
||||
fatal("Duplicate --port option");
|
||||
}
|
||||
if (cgi) {
|
||||
fatal("Cannot specifiy a port for CGI operation");
|
||||
}
|
||||
port = strtoint(optarg, 1, 65535);
|
||||
} else if (!idx--) {
|
||||
// Service
|
||||
struct Service *service;
|
||||
service = newService(optarg);
|
||||
if (getRefFromHashMap(services, service->path)) {
|
||||
if (getRefFromHashMap(serviceTable, service->path)) {
|
||||
fatal("Duplicate service description for \"%s\".", service->path);
|
||||
}
|
||||
addToHashMap(services, service->path, (char *)service);
|
||||
addToHashMap(serviceTable, service->path, (char *)service);
|
||||
} else if (!idx--) {
|
||||
// Disable SSL
|
||||
if (!hasSSL) {
|
||||
|
@ -735,12 +779,26 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
info("Command line:%s", buf);
|
||||
free(buf);
|
||||
|
||||
// If the user did not register any services, provide the default service
|
||||
if (!getHashmapSize(services)) {
|
||||
addToHashMap(services, "/", (char *)newService(":LOGIN"));
|
||||
// If the user did not specify a port, use the default one
|
||||
if (!cgi && !port) {
|
||||
port = PORTNUM;
|
||||
}
|
||||
|
||||
// If the user did not register any services, provide the default service
|
||||
if (!getHashmapSize(serviceTable)) {
|
||||
addToHashMap(serviceTable, "/", (char *)newService(":LOGIN"));
|
||||
}
|
||||
enumerateServices(serviceTable);
|
||||
deleteHashMap(serviceTable);
|
||||
|
||||
// Do not allow non-root URLs for CGI operation
|
||||
if (cgi) {
|
||||
for (int i = 0; i < numServices; i++) {
|
||||
if (strcmp(services[i]->path, "/")) {
|
||||
fatal("Non-root service URLs are incompatible with CGI operation");
|
||||
}
|
||||
}
|
||||
}
|
||||
enumerateServices(services);
|
||||
deleteHashMap(services);
|
||||
|
||||
if (demonize) {
|
||||
pid_t pid;
|
||||
|
@ -763,16 +821,34 @@ static void parseArgs(int argc, char * const argv[]) {
|
|||
free((char *)pidfile);
|
||||
}
|
||||
|
||||
static void removeLimits() {
|
||||
static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
|
||||
for (int i = 0; i < sizeof(res)/sizeof(int); i++) {
|
||||
struct rlimit rl;
|
||||
getrlimit(res[i], &rl);
|
||||
if (rl.rlim_max < RLIM_INFINITY) {
|
||||
rl.rlim_max = RLIM_INFINITY;
|
||||
setrlimit(res[i], &rl);
|
||||
getrlimit(res[i], &rl);
|
||||
}
|
||||
if (rl.rlim_cur < rl.rlim_max) {
|
||||
rl.rlim_cur = rl.rlim_max;
|
||||
setrlimit(res[i], &rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[]) {
|
||||
// Disable core files
|
||||
prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
|
||||
removeLimits();
|
||||
|
||||
// Parse command line arguments
|
||||
parseArgs(argc, argv);
|
||||
|
||||
// Fork the launcher process, allowing us to drop privileges in the main
|
||||
// process.
|
||||
forkLauncher();
|
||||
int launcherFd = forkLauncher();
|
||||
dropPrivileges();
|
||||
|
||||
// Make sure that our timestamps will print in the standard format
|
||||
|
@ -780,7 +856,42 @@ int main(int argc, char * const argv[]) {
|
|||
|
||||
// Create a new web server
|
||||
Server *server;
|
||||
if (port) {
|
||||
check(server = newServer(port));
|
||||
} else {
|
||||
// For CGI operation we fork the new server, so that it runs in the
|
||||
// background.
|
||||
pid_t pid;
|
||||
int fds[2];
|
||||
check(!pipe(fds));
|
||||
check((pid = fork()) >= 0);
|
||||
if (pid) {
|
||||
// Wait for child to output initial HTML page
|
||||
char wait;
|
||||
check(!NOINTR(close(fds[1])));
|
||||
check(!NOINTR(read(fds[0], &wait, 1)));
|
||||
check(!NOINTR(close(fds[0])));
|
||||
_exit(0);
|
||||
}
|
||||
check(!NOINTR(close(fds[0])));
|
||||
check(server = newCGIServer(portMin, portMax, AJAX_TIMEOUT));
|
||||
cgiServer = server;
|
||||
|
||||
// Output a <frameset> that includes our root page
|
||||
check(port = serverGetListeningPort(server));
|
||||
extern char cgiRootStart[];
|
||||
extern char cgiRootEnd[];
|
||||
char *cgiRoot;
|
||||
check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1));
|
||||
memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart);
|
||||
puts("Content-type: text/html; charset=utf-8\r\n\r");
|
||||
printf(cgiRoot, port);
|
||||
fflush(stdout);
|
||||
free(cgiRoot);
|
||||
check(!NOINTR(close(fds[1])));
|
||||
closeAllFds((int []){ launcherFd, serverGetFd(server) }, 2);
|
||||
logSetLogLevel(MSG_QUIET);
|
||||
}
|
||||
serverEnableSSL(server, enableSSL);
|
||||
|
||||
// Enable SSL support (if available)
|
||||
|
@ -827,5 +938,5 @@ int main(int argc, char * const argv[]) {
|
|||
free(services);
|
||||
free(certificateDir);
|
||||
info("Done");
|
||||
exit(0);
|
||||
_exit(0);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ shellinaboxd \- publish command line shell through AJAX interface
|
|||
.B shellinaboxd
|
||||
[\ \fB-b\fP\ | \fB--background\fP[\fB=\fP\fIpidfile\fP]\ ]
|
||||
[\ \fB-c\fP\ | \fB--cert=\fP\fIcertdir\fP\ ]
|
||||
[\ \fB--cgi\fP[\fB=\fP\fIportrange\fP]\ ]
|
||||
[\ \fB-d\fP\ | \fB--debug\fP\ ]
|
||||
[\ \fB-f\fP\ | \fB--static-file=\fP\fIurl\fP:\fIfile\fP\ ]
|
||||
[\ \fB-g\fP\ | \fB--group=\fP\fIgid\fP\ ]
|
||||
|
@ -136,6 +137,32 @@ certificate. Due to this usability problem, and due to the perceived
|
|||
security implications, the use of auto-generated self-signed
|
||||
certificates is intended for testing or in intranet deployments, only.
|
||||
.TP
|
||||
\fB--cgi\fP[\fB=\fP\fIportrange\fP]
|
||||
Instead of running
|
||||
.B shellinaboxd
|
||||
as a permanent process, it can be demand-loaded as a CGI web server
|
||||
extension. When doing so, it will spawn a server that lives for the
|
||||
duration of the user's session. If an optional
|
||||
.I portrange
|
||||
of the form
|
||||
.BR MINPORT-MAXPORT
|
||||
has been provided, the server limits itself to these port numbers. They
|
||||
should be configured to pass through the firewall.
|
||||
|
||||
The
|
||||
.B --cgi
|
||||
option is mutually exclusive with the
|
||||
.B --background
|
||||
and
|
||||
.B --port
|
||||
options.
|
||||
|
||||
In order to be useful as a CGI script, the
|
||||
.B shellinaboxd
|
||||
binary probably will have to be made
|
||||
.BR setuid-root .
|
||||
This is currently a discouraged configuration. Use with care.
|
||||
.TP
|
||||
\fB-d\fP\ |\ \fB--debug\fP
|
||||
Enables debugging mode, resulting in lots of log messages on
|
||||
.IR stderr .
|
||||
|
@ -201,13 +228,8 @@ the server drops most privileges at start up. Unless overridden by the
|
|||
option, it switches to
|
||||
.BR nogroup .
|
||||
|
||||
When already running as an unprivileged user, the group remains
|
||||
unchanged, unless a change was explicitly requested with the
|
||||
.B --group
|
||||
option. On most UNIX systems, the latter is only possible, if the
|
||||
binary has been made
|
||||
.BR setuid-root .
|
||||
This is a non-standard configuration.
|
||||
When already running as an unprivileged user, group changes are not
|
||||
possible.
|
||||
|
||||
If running with SSL/TLS support enabled, the certificates must be
|
||||
accessible to the unprivileged user and/or group that the daemon
|
||||
|
|
|
@ -225,7 +225,8 @@ VT100.prototype.initializeElements = function(container) {
|
|||
}
|
||||
this.container.innerHTML =
|
||||
'<div id="reconnect" style="visibility: hidden">' +
|
||||
'<input type="button" value="Connect" />' +
|
||||
'<input type="button" value="Connect" ' +
|
||||
'onsubmit="return false" />' +
|
||||
'</div>' +
|
||||
'<div id="menu"></div>' +
|
||||
'<div id="scrollable">' +
|
||||
|
@ -322,8 +323,9 @@ VT100.prototype.initializeElements = function(container) {
|
|||
this.addListener(this.reconnectBtn.firstChild, 'click',
|
||||
function(vt100) {
|
||||
return function() {
|
||||
vt100.reconnect();
|
||||
var rc = vt100.reconnect();
|
||||
vt100.input.focus();
|
||||
return rc;
|
||||
}
|
||||
}(this));
|
||||
|
||||
|
@ -398,6 +400,7 @@ VT100.prototype.getCurrentComputedStyle = function(elem, style) {
|
|||
};
|
||||
|
||||
VT100.prototype.reconnect = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
VT100.prototype.showReconnect = function(state) {
|
||||
|
|
Loading…
Reference in a new issue