- 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/service.h \
|
||||||
shellinabox/session.c \
|
shellinabox/session.c \
|
||||||
shellinabox/session.h \
|
shellinabox/session.h \
|
||||||
|
shellinabox/cgi_root.html \
|
||||||
shellinabox/root_page.html \
|
shellinabox/root_page.html \
|
||||||
shellinabox/vt100.js \
|
shellinabox/vt100.js \
|
||||||
shellinabox/shell_in_a_box.js \
|
shellinabox/shell_in_a_box.js \
|
||||||
|
|
|
@ -73,7 +73,8 @@ am__dirstamp = $(am__leading_dot)dirstamp
|
||||||
am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \
|
am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \
|
||||||
externalfile.$(OBJEXT) launcher.$(OBJEXT) privileges.$(OBJEXT) \
|
externalfile.$(OBJEXT) launcher.$(OBJEXT) privileges.$(OBJEXT) \
|
||||||
service.$(OBJEXT) session.$(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/shell_in_a_box.$(OBJEXT) \
|
||||||
shellinabox/styles.$(OBJEXT) shellinabox/favicon.$(OBJEXT) \
|
shellinabox/styles.$(OBJEXT) shellinabox/favicon.$(OBJEXT) \
|
||||||
shellinabox/beep.$(OBJEXT)
|
shellinabox/beep.$(OBJEXT)
|
||||||
|
@ -299,6 +300,7 @@ shellinaboxd_SOURCES = shellinabox/shellinaboxd.c \
|
||||||
shellinabox/service.h \
|
shellinabox/service.h \
|
||||||
shellinabox/session.c \
|
shellinabox/session.c \
|
||||||
shellinabox/session.h \
|
shellinabox/session.h \
|
||||||
|
shellinabox/cgi_root.html \
|
||||||
shellinabox/root_page.html \
|
shellinabox/root_page.html \
|
||||||
shellinabox/vt100.js \
|
shellinabox/vt100.js \
|
||||||
shellinabox/shell_in_a_box.js \
|
shellinabox/shell_in_a_box.js \
|
||||||
|
@ -394,6 +396,8 @@ shellinabox/$(am__dirstamp):
|
||||||
shellinabox/$(DEPDIR)/$(am__dirstamp):
|
shellinabox/$(DEPDIR)/$(am__dirstamp):
|
||||||
@$(MKDIR_P) shellinabox/$(DEPDIR)
|
@$(MKDIR_P) shellinabox/$(DEPDIR)
|
||||||
@: > shellinabox/$(DEPDIR)/$(am__dirstamp)
|
@: > shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||||
|
shellinabox/cgi_root.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||||
|
shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||||
shellinabox/root_page.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
shellinabox/root_page.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||||
shellinabox/$(DEPDIR)/$(am__dirstamp)
|
shellinabox/$(DEPDIR)/$(am__dirstamp)
|
||||||
shellinabox/vt100.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
shellinabox/vt100.$(OBJEXT): shellinabox/$(am__dirstamp) \
|
||||||
|
@ -413,6 +417,7 @@ shellinaboxd$(EXEEXT): $(shellinaboxd_OBJECTS) $(shellinaboxd_DEPENDENCIES)
|
||||||
mostlyclean-compile:
|
mostlyclean-compile:
|
||||||
-rm -f *.$(OBJEXT)
|
-rm -f *.$(OBJEXT)
|
||||||
-rm -f shellinabox/beep.$(OBJEXT)
|
-rm -f shellinabox/beep.$(OBJEXT)
|
||||||
|
-rm -f shellinabox/cgi_root.$(OBJEXT)
|
||||||
-rm -f shellinabox/favicon.$(OBJEXT)
|
-rm -f shellinabox/favicon.$(OBJEXT)
|
||||||
-rm -f shellinabox/root_page.$(OBJEXT)
|
-rm -f shellinabox/root_page.$(OBJEXT)
|
||||||
-rm -f shellinabox/shell_in_a_box.$(OBJEXT)
|
-rm -f shellinabox/shell_in_a_box.$(OBJEXT)
|
||||||
|
|
|
@ -64,8 +64,11 @@ typedef struct ServerConnection ServerConnection;
|
||||||
typedef struct Server Server;
|
typedef struct Server Server;
|
||||||
typedef struct URL URL;
|
typedef struct URL URL;
|
||||||
|
|
||||||
|
Server *newCGIServer(int portMin, int portMax, int timeout);
|
||||||
Server *newServer(int port);
|
Server *newServer(int port);
|
||||||
void deleteServer(Server *server);
|
void deleteServer(Server *server);
|
||||||
|
int serverGetListeningPort(Server *server);
|
||||||
|
int serverGetFd(Server *server);
|
||||||
void serverRegisterHttpHandler(Server *server, const char *url,
|
void serverRegisterHttpHandler(Server *server, const char *url,
|
||||||
int (*handler)(HttpConnection *, void *,
|
int (*handler)(HttpConnection *, void *,
|
||||||
const char *, int), void *arg);
|
const char *, int), void *arg);
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
newCGIServer
|
||||||
newServer
|
newServer
|
||||||
deleteServer
|
deleteServer
|
||||||
|
serverGetListeningPort
|
||||||
|
serverGetFd
|
||||||
serverRegisterHttpHandler
|
serverRegisterHttpHandler
|
||||||
serverRegisterStreamingHttpHandler
|
serverRegisterStreamingHttpHandler
|
||||||
serverAddConnection
|
serverAddConnection
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/poll.h>
|
#include <sys/poll.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
@ -166,6 +167,58 @@ static int serverQuitHandler(struct HttpConnection *http, void *arg) {
|
||||||
return HTTP_DONE;
|
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 *newServer(int port) {
|
||||||
struct Server *server;
|
struct Server *server;
|
||||||
check(server = malloc(sizeof(struct Server)));
|
check(server = malloc(sizeof(struct Server)));
|
||||||
|
@ -177,6 +230,7 @@ void initServer(struct Server *server, int port) {
|
||||||
server->port = port;
|
server->port = port;
|
||||||
server->looping = 0;
|
server->looping = 0;
|
||||||
server->exitAll = 0;
|
server->exitAll = 0;
|
||||||
|
server->serverTimeout = -1;
|
||||||
server->serverFd = -1;
|
server->serverFd = -1;
|
||||||
server->numericHosts = 0;
|
server->numericHosts = 0;
|
||||||
server->pollFds = NULL;
|
server->pollFds = NULL;
|
||||||
|
@ -208,6 +262,14 @@ void deleteServer(struct Server *server) {
|
||||||
free(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,
|
struct ServerConnection *serverAddConnection(struct Server *server, int fd,
|
||||||
int (*handleConnection)(struct ServerConnection *c,
|
int (*handleConnection)(struct ServerConnection *c,
|
||||||
void *arg, short *events,
|
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) {
|
if (timeout >= 0) {
|
||||||
// Wait at least one second longer than needed, so that even if
|
// Wait at least one second longer than needed, so that even if
|
||||||
// poll() decides to return a second early (due to possible rounding
|
// poll() decides to return a second early (due to possible rounding
|
||||||
|
@ -406,6 +476,12 @@ void serverLoop(struct Server *server) {
|
||||||
INITIAL_TIMEOUT);
|
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;
|
for (int i = 1;
|
||||||
(isTimeout || eventCount > 0) && i <= server->numConnections;
|
(isTimeout || eventCount > 0) && i <= server->numConnections;
|
||||||
|
|
|
@ -68,6 +68,7 @@ struct Server {
|
||||||
int port;
|
int port;
|
||||||
int looping;
|
int looping;
|
||||||
int exitAll;
|
int exitAll;
|
||||||
|
int serverTimeout;
|
||||||
int serverFd;
|
int serverFd;
|
||||||
int numericHosts;
|
int numericHosts;
|
||||||
struct pollfd *pollFds;
|
struct pollfd *pollFds;
|
||||||
|
@ -77,10 +78,13 @@ struct Server {
|
||||||
struct SSLSupport ssl;
|
struct SSLSupport ssl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Server *newCGIServer(int portMin, int portMax, int timeout);
|
||||||
struct Server *newServer(int port);
|
struct Server *newServer(int port);
|
||||||
void initServer(struct Server *server, int port);
|
void initServer(struct Server *server, int port);
|
||||||
void destroyServer(struct Server *server);
|
void destroyServer(struct Server *server);
|
||||||
void deleteServer(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,
|
void serverRegisterHttpHandler(struct Server *server, const char *url,
|
||||||
int (*handler)(struct HttpConnection *, void *,
|
int (*handler)(struct HttpConnection *, void *,
|
||||||
const char *, int), void *arg);
|
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 warn(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||||
void error(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 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 logIsDebug(void);
|
||||||
int logIsInfo(void);
|
int logIsInfo(void);
|
||||||
int logIsWarn(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));
|
extern DIR *fdopendir(int) __attribute__((weak));
|
||||||
|
|
||||||
static int launcher = -1;
|
static int launcher = -1;
|
||||||
|
static uid_t restricted;
|
||||||
|
|
||||||
|
|
||||||
static void *loadSymbol(const char *lib, const char *fn) {
|
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);
|
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,
|
static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
|
||||||
const char *peerName) {
|
const char *peerName) {
|
||||||
int slave;
|
int slave;
|
||||||
|
@ -323,27 +378,7 @@ static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
|
||||||
(*utmp)->utmpx.ut_pid = pid;
|
(*utmp)->utmpx.ut_pid = pid;
|
||||||
(*utmp)->pty = slave;
|
(*utmp)->pty = slave;
|
||||||
|
|
||||||
// Close all file handles. If possible, scan through "/proc/self/fd" as
|
closeAllFds((int []){ slave }, 1);
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Become the session/process-group leader
|
// Become the session/process-group leader
|
||||||
setsid();
|
setsid();
|
||||||
|
@ -958,7 +993,7 @@ static void launcherDaemon(int fd) {
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void forkLauncher(void) {
|
int forkLauncher(void) {
|
||||||
int pair[2];
|
int pair[2];
|
||||||
check(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
|
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".
|
// switch back to root, which is necessary for launching "login".
|
||||||
lowerPrivileges();
|
lowerPrivileges();
|
||||||
NOINTR(close(pair[0]));
|
NOINTR(close(pair[0]));
|
||||||
|
closeAllFds((int []){ pair[1] }, 1);
|
||||||
launcherDaemon(pair[1]);
|
launcherDaemon(pair[1]);
|
||||||
fatal("exit() failed!");
|
fatal("exit() failed!");
|
||||||
case -1:
|
case -1:
|
||||||
fatal("fork() failed!");
|
fatal("fork() failed!");
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
NOINTR(close(pair[1]));
|
NOINTR(close(pair[1]));
|
||||||
launcher = pair[0];
|
launcher = pair[0];
|
||||||
return;
|
return launcher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,10 @@ struct LaunchRequest {
|
||||||
char peerName[128];
|
char peerName[128];
|
||||||
};
|
};
|
||||||
|
|
||||||
int supportsPAM(void);
|
int supportsPAM(void);
|
||||||
int launchChild(int service, struct Session *session);
|
int launchChild(int service, struct Session *session);
|
||||||
void setWindowSize(int pty, int width, int height);
|
void setWindowSize(int pty, int width, int height);
|
||||||
void forkLauncher(void);
|
int forkLauncher(void);
|
||||||
|
void closeAllFds(int *exceptFd, int num);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -60,40 +60,48 @@
|
||||||
|
|
||||||
int runAsUser = -1;
|
int runAsUser = -1;
|
||||||
int runAsGroup = -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
|
// Remove all supplementary groups. Allow this command to fail. That could
|
||||||
// happen if we run as an unprivileged user.
|
// happen if we run as an unprivileged user.
|
||||||
setgroups(0, (gid_t *)"");
|
setgroups(0, (gid_t *)"");
|
||||||
|
|
||||||
if (runAsGroup >= 0) {
|
if (runAsGroup >= 0) {
|
||||||
|
uid_t ru, eu, su;
|
||||||
|
getresuid(&ru, &eu, &su);
|
||||||
|
|
||||||
// Try to switch the user-provided group.
|
// Try to switch the user-provided group.
|
||||||
if (setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
if ((ru && runAsGroup != rg) ||
|
||||||
if (restricted) {
|
setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
||||||
_exit(1);
|
if (showError) {
|
||||||
} else {
|
|
||||||
fatal("Only privileged users can change their group memberships");
|
fatal("Only privileged users can change their group memberships");
|
||||||
|
} else {
|
||||||
|
_exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gid_t r, e, s;
|
if (rg) {
|
||||||
check(!getresgid(&r, &e, &s));
|
|
||||||
if (r) {
|
|
||||||
// If we were started as a set-gid binary, drop these permissions, now.
|
// If we were started as a set-gid binary, drop these permissions, now.
|
||||||
check(!setresgid(r, r, r));
|
check(!setresgid(rg, rg, rg));
|
||||||
} else {
|
} else {
|
||||||
// If we are running as root, switch to "nogroup"
|
// If we are running as root, switch to "nogroup"
|
||||||
gid_t n = getGroupId("nogroup");
|
gid_t ng = getGroupId("nogroup");
|
||||||
check(!setresgid(n, n, n));
|
check(!setresgid(ng, ng, ng));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void lowerPrivileges(void) {
|
void lowerPrivileges(void) {
|
||||||
|
uid_t r, e, g;
|
||||||
|
check(!getresuid(&r, &e, &g));
|
||||||
|
|
||||||
// Permanently lower all group permissions. We do not actually need these,
|
// Permanently lower all group permissions. We do not actually need these,
|
||||||
// as we still have "root" user privileges in our saved-uid.
|
// 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,
|
// Temporarily lower user privileges. If we used to have "root" privileges,
|
||||||
// we can later still regain them.
|
// we can later still regain them.
|
||||||
|
@ -101,10 +109,11 @@ void lowerPrivileges(void) {
|
||||||
|
|
||||||
if (runAsUser >= 0) {
|
if (runAsUser >= 0) {
|
||||||
// Try to switch to the user-provided user id.
|
// 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));
|
check(!setresuid(runAsUser, runAsUser, -1));
|
||||||
} else {
|
} else {
|
||||||
uid_t r, e, s;
|
|
||||||
check(!getresuid(&r, &e, &s));
|
|
||||||
if (r) {
|
if (r) {
|
||||||
// If we were started as a set-uid binary, temporarily lower these
|
// If we were started as a set-uid binary, temporarily lower these
|
||||||
// permissions.
|
// permissions.
|
||||||
|
@ -118,17 +127,19 @@ void lowerPrivileges(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void dropPrivileges(void) {
|
void dropPrivileges(void) {
|
||||||
|
uid_t r, e, s;
|
||||||
|
check(!getresuid(&r, &e, &s));
|
||||||
|
|
||||||
// Drop all group privileges.
|
// Drop all group privileges.
|
||||||
removeGroupPrivileges();
|
removeGroupPrivileges(1);
|
||||||
|
|
||||||
if (runAsUser >= 0) {
|
if (runAsUser >= 0) {
|
||||||
// Try to switch to the user-provided user id.
|
// 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.");
|
fatal("Only privileged users can change their user id.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uid_t r, e, s;
|
|
||||||
check(!getresuid(&r, &e, &s));
|
|
||||||
if (r) {
|
if (r) {
|
||||||
// If we were started as a set-uid binary, permanently drop these
|
// If we were started as a set-uid binary, permanently drop these
|
||||||
// permissions.
|
// permissions.
|
||||||
|
|
|
@ -50,9 +50,7 @@
|
||||||
|
|
||||||
extern int runAsUser;
|
extern int runAsUser;
|
||||||
extern int runAsGroup;
|
extern int runAsGroup;
|
||||||
extern uid_t restricted;
|
|
||||||
|
|
||||||
void removeGroupPrivileges(void);
|
|
||||||
void lowerPrivileges(void);
|
void lowerPrivileges(void);
|
||||||
void dropPrivileges(void);
|
void dropPrivileges(void);
|
||||||
const char *getUserName(uid_t uid);
|
const char *getUserName(uid_t uid);
|
||||||
|
|
|
@ -68,6 +68,11 @@
|
||||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||||
<script type="text/javascript" src="ShellInABox.js"></script>
|
<script type="text/javascript" src="ShellInABox.js"></script>
|
||||||
</head>
|
</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>
|
must be enabled for ShellInABox</noscript></body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -218,3 +218,7 @@ struct Session *findSession(int *isNew, HttpConnection *http, URL *url) {
|
||||||
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){
|
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){
|
||||||
iterateOverHashMap(sessions, fnc, arg);
|
iterateOverHashMap(sessions, fnc, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int numSessions(void) {
|
||||||
|
return getHashmapSize(sessions);
|
||||||
|
}
|
||||||
|
|
|
@ -77,5 +77,6 @@ void finishSession(struct Session *session);
|
||||||
void finishAllSessions(void);
|
void finishAllSessions(void);
|
||||||
struct Session *findSession(int *isNew, HttpConnection *http, URL *url);
|
struct Session *findSession(int *isNew, HttpConnection *http, URL *url);
|
||||||
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg);
|
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg);
|
||||||
|
int numSessions(void);
|
||||||
|
|
||||||
#endif /* SESSION_H__ */
|
#endif /* SESSION_H__ */
|
||||||
|
|
|
@ -91,11 +91,16 @@ function extend(subClass, baseClass) {
|
||||||
|
|
||||||
function ShellInABox(url, container) {
|
function ShellInABox(url, container) {
|
||||||
if (url == undefined) {
|
if (url == undefined) {
|
||||||
this.url = document.location.href;
|
this.url = document.location.href.replace(/[?#].*/, '');
|
||||||
} else {
|
} else {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
this.nextUrl = this.url;
|
if (document.location.hash != '') {
|
||||||
|
this.nextUrl = decodeURIComponent(document.location.hash).
|
||||||
|
replace(/^#/, '');
|
||||||
|
} else {
|
||||||
|
this.nextUrl = this.url;
|
||||||
|
}
|
||||||
this.session = null;
|
this.session = null;
|
||||||
this.pendingKeys = '';
|
this.pendingKeys = '';
|
||||||
this.keysInFlight = false;
|
this.keysInFlight = false;
|
||||||
|
@ -125,15 +130,24 @@ ShellInABox.prototype.sessionClosed = function() {
|
||||||
ShellInABox.prototype.reconnect = function() {
|
ShellInABox.prototype.reconnect = function() {
|
||||||
this.showReconnect(false);
|
this.showReconnect(false);
|
||||||
if (!this.session) {
|
if (!this.session) {
|
||||||
if (this.url != this.nextUrl) {
|
if (document.location.hash != '') {
|
||||||
document.location.replace(this.nextUrl);
|
// 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 {
|
} else {
|
||||||
this.pendingKeys = '';
|
if (this.url != this.nextUrl) {
|
||||||
this.keysInFlight = false;
|
document.location.replace(this.nextUrl);
|
||||||
this.reset(true);
|
} else {
|
||||||
this.sendRequest();
|
this.pendingKeys = '';
|
||||||
|
this.keysInFlight = false;
|
||||||
|
this.reset(true);
|
||||||
|
this.sendRequest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ShellInABox.prototype.sendRequest = function(request) {
|
ShellInABox.prototype.sendRequest = function(request) {
|
||||||
|
@ -176,6 +190,7 @@ ShellInABox.prototype.onReadyStateChange = function(request) {
|
||||||
}
|
}
|
||||||
} else if (request.status == 0) {
|
} else if (request.status == 0) {
|
||||||
// Time Out
|
// Time Out
|
||||||
|
this.inspect(request);/***/
|
||||||
this.sendRequest(request);
|
this.sendRequest(request);
|
||||||
} else {
|
} else {
|
||||||
this.sessionClosed();
|
this.sessionClosed();
|
||||||
|
@ -262,11 +277,22 @@ ShellInABox.prototype.resized = function(w, h) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ShellInABox.prototype.toggleSSL = function() {
|
ShellInABox.prototype.toggleSSL = function() {
|
||||||
this.nextUrl = this.nextUrl.match(/^https:/)
|
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(/^https:/, 'http:').replace(/\/*$/, '/plain')
|
||||||
: this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
|
: this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
|
||||||
|
}
|
||||||
if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
|
if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
|
||||||
this.nextUrl += '/';
|
this.nextUrl += '/';
|
||||||
}
|
}
|
||||||
if (this.session && this.nextUrl != this.url) {
|
if (this.session && this.nextUrl != this.url) {
|
||||||
alert('This change will take effect the next time you login.');
|
alert('This change will take effect the next time you login.');
|
||||||
|
@ -290,8 +316,13 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
|
||||||
// If the server supports both SSL and plain text connections,
|
// If the server supports both SSL and plain text connections,
|
||||||
// provide a menu entry to switch between the two.
|
// provide a menu entry to switch between the two.
|
||||||
var newNode = document.createElement('li');
|
var newNode = document.createElement('li');
|
||||||
newNode.innerHTML =
|
var isSecure;
|
||||||
(this.nextUrl.match(/^https:/) ? '✔ ' : '') + 'Secure';
|
if (document.location.href != '') {
|
||||||
|
isSecure = !this.nextUrl.match(/\?plain$/);
|
||||||
|
} else {
|
||||||
|
isSecure = this.nextUrl.match(/^https:/);
|
||||||
|
}
|
||||||
|
newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
|
||||||
if (node.nextSibling) {
|
if (node.nextSibling) {
|
||||||
entries.insertBefore(newNode, node.nextSibling);
|
entries.insertBefore(newNode, node.nextSibling);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -70,11 +71,14 @@
|
||||||
#define PORTNUM 4200
|
#define PORTNUM 4200
|
||||||
#define MAX_RESPONSE 2048
|
#define MAX_RESPONSE 2048
|
||||||
|
|
||||||
static int port = PORTNUM;
|
static int port;
|
||||||
|
static int portMin;
|
||||||
|
static int portMax;
|
||||||
static int numericHosts = 0;
|
static int numericHosts = 0;
|
||||||
static int enableSSL = 1;
|
static int enableSSL = 1;
|
||||||
static char *certificateDir;
|
static char *certificateDir;
|
||||||
static HashMap *externalFiles;
|
static HashMap *externalFiles;
|
||||||
|
static Server *cgiServer;
|
||||||
|
|
||||||
|
|
||||||
static char *jsonEscape(const char *buf, int len) {
|
static char *jsonEscape(const char *buf, int len) {
|
||||||
|
@ -288,6 +292,10 @@ static int dataHandler(HttpConnection *http, struct Service *service,
|
||||||
int isNew;
|
int isNew;
|
||||||
struct Session *session = findSession(&isNew, http, url);
|
struct Session *session = findSession(&isNew, http, url);
|
||||||
if (session == NULL) {
|
if (session == NULL) {
|
||||||
|
if (cgiServer && numSessions() == 0) {
|
||||||
|
// CGI servers only ever serve a single session
|
||||||
|
serverExitLoop(cgiServer, 1);
|
||||||
|
}
|
||||||
httpSendReply(http, 400, "Bad Request", NULL);
|
httpSendReply(http, 400, "Bad Request", NULL);
|
||||||
return HTTP_DONE;
|
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
|
// Create a new session, if the client did not provide an existing one
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
if (cgiServer && numSessions() > 1) {
|
||||||
|
deleteSession(session);
|
||||||
|
httpSendReply(http, 400, "Bad Request", NULL);
|
||||||
|
return HTTP_DONE;
|
||||||
|
}
|
||||||
debug("Creating new child process");
|
debug("Creating new child process");
|
||||||
if (launchChild(service->id, session) < 0) {
|
if (launchChild(service->id, session) < 0) {
|
||||||
deleteSession(session);
|
deleteSession(session);
|
||||||
|
@ -540,6 +553,7 @@ static void usage(void) {
|
||||||
"List of command line options:\n"
|
"List of command line options:\n"
|
||||||
" -b, --background[=PIDFILE] run in background\n"
|
" -b, --background[=PIDFILE] run in background\n"
|
||||||
"%s"
|
"%s"
|
||||||
|
" --cgi[=PORTMIN-PORTMAX] run as CGI\n"
|
||||||
" -d, --debug enable debug mode\n"
|
" -d, --debug enable debug mode\n"
|
||||||
" -f, --static-file=URL:FILE serve static file from URL path\n"
|
" -f, --static-file=URL:FILE serve static file from URL path\n"
|
||||||
" -g, --group=GID switch to this group (default: %s)\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;
|
enableSSL = 0;
|
||||||
}
|
}
|
||||||
int demonize = 0;
|
int demonize = 0;
|
||||||
|
int cgi = 0;
|
||||||
const char *pidfile = NULL;
|
const char *pidfile = NULL;
|
||||||
int verbosity = MSG_DEFAULT;
|
int verbosity = MSG_DEFAULT;
|
||||||
externalFiles = newHashMap(destroyExternalFileHashEntry, NULL);
|
externalFiles = newHashMap(destroyExternalFileHashEntry, NULL);
|
||||||
HashMap *services = newHashMap(destroyServiceHashEntry, NULL);
|
HashMap *serviceTable = newHashMap(destroyServiceHashEntry, NULL);
|
||||||
for (;;) {
|
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[] = {
|
static struct option options[] = {
|
||||||
{ "help", 0, 0, 'h' },
|
{ "help", 0, 0, 'h' },
|
||||||
{ "background", 2, 0, 'b' },
|
{ "background", 2, 0, 'b' },
|
||||||
{ "cert", 1, 0, 'c' },
|
{ "cert", 1, 0, 'c' },
|
||||||
|
{ "cgi", 2, 0, 0 },
|
||||||
{ "debug", 0, 0, 'd' },
|
{ "debug", 0, 0, 'd' },
|
||||||
{ "static-file", 1, 0, 'f' },
|
{ "static-file", 1, 0, 'f' },
|
||||||
{ "group", 1, 0, 'g' },
|
{ "group", 1, 0, 'g' },
|
||||||
|
@ -634,6 +650,9 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
exit(idx != -1);
|
exit(idx != -1);
|
||||||
} else if (!idx--) {
|
} else if (!idx--) {
|
||||||
// Background
|
// Background
|
||||||
|
if (cgi) {
|
||||||
|
fatal("CGI and background operations are mutually exclusive");
|
||||||
|
}
|
||||||
demonize = 1;
|
demonize = 1;
|
||||||
if (optarg && pidfile) {
|
if (optarg && pidfile) {
|
||||||
fatal("Only one pidfile can be given");
|
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");
|
fatal("Only one certificate directory can be selected");
|
||||||
}
|
}
|
||||||
check(certificateDir = strdup(optarg));
|
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--) {
|
} else if (!idx--) {
|
||||||
// Debug
|
// Debug
|
||||||
if (!logIsDefault() && !logIsDebug()) {
|
if (!logIsDefault() && !logIsDebug()) {
|
||||||
|
@ -682,15 +720,21 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
numericHosts = 1;
|
numericHosts = 1;
|
||||||
} else if (!idx--) {
|
} else if (!idx--) {
|
||||||
// Port
|
// Port
|
||||||
|
if (port) {
|
||||||
|
fatal("Duplicate --port option");
|
||||||
|
}
|
||||||
|
if (cgi) {
|
||||||
|
fatal("Cannot specifiy a port for CGI operation");
|
||||||
|
}
|
||||||
port = strtoint(optarg, 1, 65535);
|
port = strtoint(optarg, 1, 65535);
|
||||||
} else if (!idx--) {
|
} else if (!idx--) {
|
||||||
// Service
|
// Service
|
||||||
struct Service *service;
|
struct Service *service;
|
||||||
service = newService(optarg);
|
service = newService(optarg);
|
||||||
if (getRefFromHashMap(services, service->path)) {
|
if (getRefFromHashMap(serviceTable, service->path)) {
|
||||||
fatal("Duplicate service description for \"%s\".", 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--) {
|
} else if (!idx--) {
|
||||||
// Disable SSL
|
// Disable SSL
|
||||||
if (!hasSSL) {
|
if (!hasSSL) {
|
||||||
|
@ -735,12 +779,26 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
info("Command line:%s", buf);
|
info("Command line:%s", buf);
|
||||||
free(buf);
|
free(buf);
|
||||||
|
|
||||||
// If the user did not register any services, provide the default service
|
// If the user did not specify a port, use the default one
|
||||||
if (!getHashmapSize(services)) {
|
if (!cgi && !port) {
|
||||||
addToHashMap(services, "/", (char *)newService(":LOGIN"));
|
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) {
|
if (demonize) {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
@ -763,16 +821,34 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
free((char *)pidfile);
|
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[]) {
|
int main(int argc, char * const argv[]) {
|
||||||
// Disable core files
|
// Disable core files
|
||||||
prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
|
prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
|
||||||
|
removeLimits();
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
parseArgs(argc, argv);
|
parseArgs(argc, argv);
|
||||||
|
|
||||||
// Fork the launcher process, allowing us to drop privileges in the main
|
// Fork the launcher process, allowing us to drop privileges in the main
|
||||||
// process.
|
// process.
|
||||||
forkLauncher();
|
int launcherFd = forkLauncher();
|
||||||
dropPrivileges();
|
dropPrivileges();
|
||||||
|
|
||||||
// Make sure that our timestamps will print in the standard format
|
// 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
|
// Create a new web server
|
||||||
Server *server;
|
Server *server;
|
||||||
check(server = newServer(port));
|
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);
|
serverEnableSSL(server, enableSSL);
|
||||||
|
|
||||||
// Enable SSL support (if available)
|
// Enable SSL support (if available)
|
||||||
|
@ -827,5 +938,5 @@ int main(int argc, char * const argv[]) {
|
||||||
free(services);
|
free(services);
|
||||||
free(certificateDir);
|
free(certificateDir);
|
||||||
info("Done");
|
info("Done");
|
||||||
exit(0);
|
_exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ shellinaboxd \- publish command line shell through AJAX interface
|
||||||
.B shellinaboxd
|
.B shellinaboxd
|
||||||
[\ \fB-b\fP\ | \fB--background\fP[\fB=\fP\fIpidfile\fP]\ ]
|
[\ \fB-b\fP\ | \fB--background\fP[\fB=\fP\fIpidfile\fP]\ ]
|
||||||
[\ \fB-c\fP\ | \fB--cert=\fP\fIcertdir\fP\ ]
|
[\ \fB-c\fP\ | \fB--cert=\fP\fIcertdir\fP\ ]
|
||||||
|
[\ \fB--cgi\fP[\fB=\fP\fIportrange\fP]\ ]
|
||||||
[\ \fB-d\fP\ | \fB--debug\fP\ ]
|
[\ \fB-d\fP\ | \fB--debug\fP\ ]
|
||||||
[\ \fB-f\fP\ | \fB--static-file=\fP\fIurl\fP:\fIfile\fP\ ]
|
[\ \fB-f\fP\ | \fB--static-file=\fP\fIurl\fP:\fIfile\fP\ ]
|
||||||
[\ \fB-g\fP\ | \fB--group=\fP\fIgid\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
|
security implications, the use of auto-generated self-signed
|
||||||
certificates is intended for testing or in intranet deployments, only.
|
certificates is intended for testing or in intranet deployments, only.
|
||||||
.TP
|
.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
|
\fB-d\fP\ |\ \fB--debug\fP
|
||||||
Enables debugging mode, resulting in lots of log messages on
|
Enables debugging mode, resulting in lots of log messages on
|
||||||
.IR stderr .
|
.IR stderr .
|
||||||
|
@ -201,13 +228,8 @@ the server drops most privileges at start up. Unless overridden by the
|
||||||
option, it switches to
|
option, it switches to
|
||||||
.BR nogroup .
|
.BR nogroup .
|
||||||
|
|
||||||
When already running as an unprivileged user, the group remains
|
When already running as an unprivileged user, group changes are not
|
||||||
unchanged, unless a change was explicitly requested with the
|
possible.
|
||||||
.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.
|
|
||||||
|
|
||||||
If running with SSL/TLS support enabled, the certificates must be
|
If running with SSL/TLS support enabled, the certificates must be
|
||||||
accessible to the unprivileged user and/or group that the daemon
|
accessible to the unprivileged user and/or group that the daemon
|
||||||
|
|
|
@ -225,7 +225,8 @@ VT100.prototype.initializeElements = function(container) {
|
||||||
}
|
}
|
||||||
this.container.innerHTML =
|
this.container.innerHTML =
|
||||||
'<div id="reconnect" style="visibility: hidden">' +
|
'<div id="reconnect" style="visibility: hidden">' +
|
||||||
'<input type="button" value="Connect" />' +
|
'<input type="button" value="Connect" ' +
|
||||||
|
'onsubmit="return false" />' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div id="menu"></div>' +
|
'<div id="menu"></div>' +
|
||||||
'<div id="scrollable">' +
|
'<div id="scrollable">' +
|
||||||
|
@ -322,8 +323,9 @@ VT100.prototype.initializeElements = function(container) {
|
||||||
this.addListener(this.reconnectBtn.firstChild, 'click',
|
this.addListener(this.reconnectBtn.firstChild, 'click',
|
||||||
function(vt100) {
|
function(vt100) {
|
||||||
return function() {
|
return function() {
|
||||||
vt100.reconnect();
|
var rc = vt100.reconnect();
|
||||||
vt100.input.focus();
|
vt100.input.focus();
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
}(this));
|
}(this));
|
||||||
|
|
||||||
|
@ -398,6 +400,7 @@ VT100.prototype.getCurrentComputedStyle = function(elem, style) {
|
||||||
};
|
};
|
||||||
|
|
||||||
VT100.prototype.reconnect = function() {
|
VT100.prototype.reconnect = function() {
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
VT100.prototype.showReconnect = function(state) {
|
VT100.prototype.showReconnect = function(state) {
|
||||||
|
|
Loading…
Reference in a new issue