1 // shellinaboxd.c -- A custom web server that makes command line applications
2 // available as AJAX web applications.
3 // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License version 2 as
7 // published by the Free Software Foundation.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 // In addition to these license terms, the author grants the following
21 // If you modify this program, or any covered work, by linking or
22 // combining it with the OpenSSL project's OpenSSL library (or a
23 // modified version of that library), containing parts covered by the
24 // terms of the OpenSSL or SSLeay licenses, the author
25 // grants you additional permission to convey the resulting work.
26 // Corresponding Source for a non-source form of such a combination
27 // shall include the source code for the parts of OpenSSL used as well
28 // as that of the covered work.
30 // You may at your option choose to remove this additional permission from
31 // the work, or from any part of it.
33 // It is possible to build this program in a way that it loads OpenSSL
34 // libraries at run-time. If doing so, the following notices are required
35 // by the OpenSSL and SSLeay licenses:
37 // This product includes software developed by the OpenSSL Project
38 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
40 // This product includes cryptographic software written by Eric Young
41 // (eay@cryptsoft.com)
44 // The most up-to-date version of this program is always available from
45 // http://shellinabox.com
59 #include <sys/resource.h>
60 #include <sys/types.h>
64 #ifdef HAVE_SYS_PRCTL_H
65 #include <sys/prctl.h>
68 #include "libhttp/http.h"
69 #include "logging/logging.h"
70 #include "shellinabox/externalfile.h"
71 #include "shellinabox/launcher.h"
72 #include "shellinabox/privileges.h"
73 #include "shellinabox/service.h"
74 #include "shellinabox/session.h"
77 #define MAX_RESPONSE 2048
82 static int localhostOnly = 0;
83 static int noBeep = 0;
84 static int numericHosts = 0;
85 static int enableSSL = 1;
86 static int enableSSLMenu = 1;
87 static int linkifyURLs = 1;
88 static char *certificateDir;
89 static int certificateFd = -1;
90 static HashMap *externalFiles;
91 static Server *cgiServer;
92 static char *cgiSessionKey;
93 static int cgiSessions;
95 static char *jsonEscape(const char *buf, int len) {
96 static const char *hexDigit = "0123456789ABCDEF";
98 // Determine the space that is needed to encode the buffer
100 const char *ptr = buf;
101 for (int i = 0; i < len; i++) {
102 unsigned char ch = *(unsigned char *)ptr++;
105 case '\b': case '\f': case '\n': case '\r': case '\t':
112 } else if (ch == '"' || ch == '\\' || ch == '/') {
114 } else if (ch > '\x7F') {
121 // Encode the buffer using JSON string escaping
123 check(result = malloc(count + 1));
126 for (int i = 0; i < len; i++) {
127 unsigned char ch = *(unsigned char *)ptr++;
131 case '\b': *dst++ = 'b'; break;
132 case '\f': *dst++ = 'f'; break;
133 case '\n': *dst++ = 'n'; break;
134 case '\r': *dst++ = 'r'; break;
135 case '\t': *dst++ = 't'; break;
141 *dst++ = hexDigit[ch >> 4];
142 *dst++ = hexDigit[ch & 0xF];
145 } else if (ch == '"' || ch == '\\' || ch == '/') {
148 } else if (ch > '\x7F') {
159 static int printfUnchecked(const char *format, ...) {
160 // Some Linux distributions enable -Wformat=2 by default. This is a
161 // very unfortunate decision, as that option generates a lot of false
162 // positives. We try to work around the problem by defining an unchecked
163 // version of "printf()"
165 va_start(ap, format);
166 int rc = vprintf(format, ap);
171 static int completePendingRequest(struct Session *session,
172 const char *buf, int len, int maxLength) {
173 // If there is no pending HTTP request, save the data and return
175 if (!session->http) {
177 if (session->buffered) {
178 check(session->buffered = realloc(session->buffered,
179 session->len + len));
180 memcpy(session->buffered + session->len, buf, len);
183 check(session->buffered = malloc(len));
184 memcpy(session->buffered, buf, len);
189 // If we have a pending HTTP request, we can reply to it, now.
191 if (session->buffered) {
192 check(session->buffered = realloc(session->buffered,
193 session->len + len));
194 memcpy(session->buffered + session->len, buf, len);
196 if (maxLength > 0 && session->len > maxLength) {
197 data = jsonEscape(session->buffered, maxLength);
198 session->len -= maxLength;
199 memmove(session->buffered, session->buffered + maxLength,
202 data = jsonEscape(session->buffered, session->len);
203 free(session->buffered);
204 session->buffered = NULL;
208 if (maxLength > 0 && len > maxLength) {
209 session->len = len - maxLength;
210 check(session->buffered = malloc(session->len));
211 memcpy(session->buffered, buf + maxLength, session->len);
212 data = jsonEscape(buf, maxLength);
214 data = jsonEscape(buf, len);
218 char *json = stringPrintf(NULL, "{"
219 "\"session\":\"%s\","
222 session->sessionKey, data);
224 HttpConnection *http = session->http;
225 char *response = stringPrintf(NULL,
226 "HTTP/1.1 200 OK\r\n"
227 "Content-Type: application/json; "
229 "Content-Length: %ld\r\n"
230 "Cache-Control: no-cache\r\n"
234 strcmp(httpGetMethod(http),
235 "HEAD") ? json : "");
237 session->http = NULL;
238 httpTransfer(http, response, strlen(response));
240 if (session->done && !session->buffered) {
241 finishSession(session);
247 static void sessionDone(void *arg) {
248 debug("Child terminated");
249 struct Session *session = (struct Session *)arg;
251 addToGraveyard(session);
252 completePendingRequest(session, "", 0, INT_MAX);
255 static int handleSession(struct ServerConnection *connection, void *arg,
256 short *events, short revents) {
257 struct Session *session = (struct Session *)arg;
258 session->connection = connection;
259 int len = MAX_RESPONSE - session->len;
265 if (revents & POLLIN) {
266 bytes = NOINTR(read(session->pty, buf, len));
271 int timedOut = serverGetTimeout(connection) < 0;
272 if (bytes || timedOut) {
273 if (!session->http && timedOut) {
274 debug("Timeout. Closing session.");
277 check(!session->done);
278 check(completePendingRequest(session, buf, bytes, MAX_RESPONSE));
279 connection = serverGetConnection(session->server,
282 session->connection = connection;
283 if (session->len >= MAX_RESPONSE) {
284 serverConnectionSetEvents(session->server, connection, 0);
286 serverSetTimeout(connection, AJAX_TIMEOUT);
293 static int invalidatePendingHttpSession(void *arg, const char *key,
295 struct Session *session = *(struct Session **)value;
296 if (session->http && session->http == (HttpConnection *)arg) {
297 debug("Clearing pending HTTP connection for session %s", key);
298 session->http = NULL;
299 serverDeleteConnection(session->server, session->pty);
301 // Return zero in order to remove this HTTP from the "session" hashmap
305 // If the session is still in use, do not remove it from the "sessions" map
309 static int dataHandler(HttpConnection *http, struct Service *service,
310 const char *buf, int len, URL *url) {
312 // Somebody unexpectedly closed our http connection (e.g. because of a
313 // timeout). This is the last notification that we will get.
315 iterateOverSessions(invalidatePendingHttpSession, http);
319 // Find an existing session, or create the record for a new one
321 struct Session *session = findCGISession(&isNew, http, url, cgiSessionKey);
322 if (session == NULL) {
323 httpSendReply(http, 400, "Bad Request", NO_MSG);
328 if (!isNew && strcmp(session->peerName, httpGetPeerName(http))) {
329 error("Peername changed from %s to %s",
330 session->peerName, httpGetPeerName(http));
331 httpSendReply(http, 400, "Bad Request", NO_MSG);
335 const HashMap *args = urlGetArgs(session->url);
336 int oldWidth = session->width;
337 int oldHeight = session->height;
338 const char *width = getFromHashMap(args, "width");
339 const char *height = getFromHashMap(args, "height");
340 const char *keys = getFromHashMap(args, "keys");
342 // Adjust window dimensions if provided by client
343 if (width && height) {
344 session->width = atoi(width);
345 session->height = atoi(height);
348 // Create a new session, if the client did not provide an existing one
350 if (cgiServer && cgiSessions++) {
351 serverExitLoop(cgiServer, 1);
352 abandonSession(session);
353 httpSendReply(http, 400, "Bad Request", NO_MSG);
356 session->http = http;
357 if (launchChild(service->id, session) < 0) {
358 abandonSession(session);
359 httpSendReply(http, 500, "Internal Error", NO_MSG);
365 session->connection = serverAddConnection(httpGetServer(http),
366 session->pty, handleSession,
367 sessionDone, session);
368 serverSetTimeout(session->connection, AJAX_TIMEOUT);
371 // Reset window dimensions of the pseudo TTY, if changed since last time set.
372 if (session->width > 0 && session->height > 0 &&
373 (session->width != oldWidth || session->height != oldHeight)) {
374 debug("Window size changed to %dx%d", session->width, session->height);
375 setWindowSize(session->pty, session->width, session->height);
378 // Process keypresses, if any. Then send a synchronous reply.
381 check(keyCodes = malloc(strlen(keys)/2));
383 for (const unsigned char *ptr = (const unsigned char *)keys; ;) {
384 unsigned c0 = *ptr++;
385 if (c0 < '0' || (c0 > '9' && c0 < 'A') ||
386 (c0 > 'F' && c0 < 'a') || c0 > 'f') {
389 unsigned c1 = *ptr++;
390 if (c1 < '0' || (c1 > '9' && c1 < 'A') ||
391 (c1 > 'F' && c1 < 'a') || c1 > 'f') {
394 keyCodes[len++] = 16*((c0 & 0xF) + 9*(c0 > '9')) +
395 (c1 & 0xF) + 9*(c1 > '9');
397 if (write(session->pty, keyCodes, len) < 0 && errno == EAGAIN) {
398 completePendingRequest(session, "\007", 1, MAX_RESPONSE);
401 httpSendReply(http, 200, "OK", " ");
402 check(session->http != http);
405 // This request is polling for data. Finish any pending requests and
406 // queue (or process) a new one.
407 if (session->http && session->http != http &&
408 !completePendingRequest(session, "", 0, MAX_RESPONSE)) {
409 httpSendReply(http, 400, "Bad Request", NO_MSG);
412 session->http = http;
415 session->connection = serverGetConnection(session->server,
418 if (session->buffered || isNew) {
419 if (completePendingRequest(session, "", 0, MAX_RESPONSE) &&
420 session->connection) {
421 // Reset the timeout, as we just received a new request.
422 serverSetTimeout(session->connection, AJAX_TIMEOUT);
423 if (session->len < MAX_RESPONSE) {
424 // Re-enable input on the child's pty
425 serverConnectionSetEvents(session->server, session->connection,POLLIN);
429 } else if (session->connection) {
430 // Re-enable input on the child's pty
431 serverConnectionSetEvents(session->server, session->connection, POLLIN);
432 serverSetTimeout(session->connection, AJAX_TIMEOUT);
438 static void serveStaticFile(HttpConnection *http, const char *contentType,
439 const char *start, const char *end) {
440 char *response = stringPrintf(NULL,
441 "HTTP/1.1 200 OK\r\n"
442 "Content-Type: %s\r\n"
443 "Content-Length: %ld\r\n"
445 contentType, (long)(end - start));
446 int len = strlen(response);
447 if (strcmp(httpGetMethod(http), "HEAD")) {
448 check(response = realloc(response, len + (end - start)));
449 memcpy(response + len, start, end - start);
452 httpTransfer(http, response, len);
455 static const char *addr(const char *a) {
456 // Work-around for a gcc bug that could occasionally generate invalid
457 // assembly instructions when optimizing code too agressively.
462 static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
463 const char *buf, int len) {
465 URL *url = newURL(http, buf, len);
466 const HashMap *headers = httpGetHeaders(http);
467 const char *contentType = getFromHashMap(headers, "content-type");
469 // Normalize the path info
470 const char *pathInfo = urlGetPathInfo(url);
471 while (*pathInfo == '/') {
474 const char *endPathInfo;
475 for (endPathInfo = pathInfo;
476 *endPathInfo && *endPathInfo != '/';
479 int pathInfoLength = endPathInfo - pathInfo;
481 if (!pathInfoLength ||
482 (pathInfoLength == 5 && !memcmp(pathInfo, "plain", 5)) ||
483 (pathInfoLength == 6 && !memcmp(pathInfo, "secure", 6))) {
484 // The root page serves the AJAX application.
486 !strncasecmp(contentType, "application/x-www-form-urlencoded", 33)) {
487 // XMLHttpRequest carrying data between the AJAX application and the
489 return dataHandler(http, arg, buf, len, url);
491 extern char rootPageStart[];
492 extern char rootPageEnd[];
494 check(rootPage = malloc(rootPageEnd - rootPageStart + 1));
495 memcpy(rootPage, rootPageStart, rootPageEnd - rootPageStart);
496 rootPage[rootPageEnd - rootPageStart] = '\000';
497 char *html = stringPrintf(NULL, rootPage,
498 enableSSL ? "true" : "false");
499 httpSendReply(http, 200, "OK", html);
501 } else if (pathInfoLength == 8 && !memcmp(pathInfo, "beep.wav", 8)) {
502 // Serve the audio sample for the console bell.
503 extern char beepStart[];
504 extern char beepEnd[];
505 serveStaticFile(http, "audio/x-wav", beepStart, beepEnd);
506 } else if (pathInfoLength == 11 && !memcmp(pathInfo, "favicon.ico", 11)) {
508 extern char faviconStart[];
509 extern char faviconEnd[];
510 serveStaticFile(http, "image/x-icon", faviconStart, faviconEnd);
511 } else if (pathInfoLength == 14 && !memcmp(pathInfo, "ShellInABox.js", 14)) {
512 // Serve both vt100.js and shell_in_a_box.js in the same transaction.
513 // Also, indicate to the client whether the server is SSL enabled.
514 extern char vt100Start[];
515 extern char vt100End[];
516 extern char shellInABoxStart[];
517 extern char shellInABoxEnd[];
518 char *stateVars = stringPrintf(NULL,
519 "serverSupportsSSL = %s;\n"
520 "disableSSLMenu = %s;\n"
521 "suppressAllAudio = %s;\n"
522 "linkifyURLs = %d;\n\n",
523 enableSSL ? "true" : "false",
524 !enableSSLMenu ? "true" : "false",
525 noBeep ? "true" : "false",
527 int stateVarsLength = strlen(stateVars);
528 int contentLength = stateVarsLength +
529 (addr(vt100End) - addr(vt100Start)) +
530 (addr(shellInABoxEnd) - addr(shellInABoxStart));
531 char *response = stringPrintf(NULL,
532 "HTTP/1.1 200 OK\r\n"
533 "Content-Type: text/javascript; charset=utf-8\r\n"
534 "Content-Length: %d\r\n"
537 int headerLength = strlen(response);
538 if (strcmp(httpGetMethod(http), "HEAD")) {
539 check(response = realloc(response, headerLength + contentLength));
540 memcpy(memcpy(memcpy(
541 response + headerLength, stateVars, stateVarsLength)+stateVarsLength,
542 vt100Start, vt100End - vt100Start) + (vt100End - vt100Start),
543 shellInABoxStart, shellInABoxEnd - shellInABoxStart);
548 httpTransfer(http, response, headerLength + contentLength);
549 } else if (pathInfoLength == 10 && !memcmp(pathInfo, "styles.css", 10)) {
550 // Serve the style sheet.
551 extern char stylesStart[];
552 extern char stylesEnd[];
553 serveStaticFile(http, "text/css; charset=utf-8", stylesStart, stylesEnd);
555 httpSendReply(http, 404, "File not found", NO_MSG);
562 static int strtoint(const char *s, int minVal, int maxVal) {
565 fatal("Missing numeric value.");
567 long l = strtol(s, &ptr, 10);
568 if (*ptr || l < minVal || l > maxVal) {
569 fatal("Range error on numeric value \"%s\".", s);
574 static void usage(void) {
575 // Drop privileges so that we can tell which uid/gid we would normally
578 uid_t r_uid, e_uid, s_uid;
579 uid_t r_gid, e_gid, s_gid;
580 check(!getresuid(&r_uid, &e_uid, &s_uid));
581 check(!getresgid(&r_gid, &e_gid, &s_gid));
582 const char *user = getUserName(r_uid);
583 const char *group = getGroupName(r_gid);
585 message("Usage: shellinaboxd [OPTIONS]...\n"
586 "Starts an HTTP server that serves terminal emulators to AJAX "
587 "enabled browsers.\n"
589 "List of command line options:\n"
590 " -b, --background[=PIDFILE] run in background\n"
592 " --cgi[=PORTMIN-PORTMAX] run as CGI\n"
593 " -d, --debug enable debug mode\n"
594 " -f, --static-file=URL:FILE serve static file from URL path\n"
595 " -g, --group=GID switch to this group (default: %s)\n"
596 " -h, --help print this message\n"
597 " --linkify=[none|normal|agressive] default is \"normal\"\n"
598 " --localhost-only only listen on 127.0.0.1\n"
599 " --no-beep suppress all audio output\n"
600 " -n, --numeric do not resolve hostnames\n"
601 " -p, --port=PORT select a port (default: %d)\n"
602 " -s, --service=SERVICE define one or more services\n"
604 " -q, --quiet turn off all messages\n"
605 " -u, --user=UID switch to this user (default: %s)\n"
606 " -v, --verbose enable logging messages\n"
607 " --version prints version information\n"
609 "Debug, quiet, and verbose are mutually exclusive.\n"
611 "One or more --service arguments define services that should "
612 "be made available \n"
613 "through the web interface:\n"
614 " SERVICE := <url-path> ':' APP\n"
615 " APP := 'LOGIN' | USER ':' CWD ':' <cmdline>\n"
616 " USER := %s<username> ':' <groupname>\n"
617 " CWD := 'HOME' | <dir>\n"
619 "<cmdline> supports variable expansion:\n"
620 " ${columns} - number of columns\n"
622 " ${group} - group name\n"
623 " ${home} - home directory\n"
624 " ${lines} - number of rows\n"
625 " ${peer} - name of remote peer\n"
626 " ${uid} - user id\n"
627 " ${user} - user name",
628 !serverSupportsSSL() ? "" :
629 " -c, --cert=CERTDIR set certificate dir "
631 " --cert-fd=FD set certificate file from fd\n",
633 !serverSupportsSSL() ? "" :
634 " -t, --disable-ssl disable transparent SSL support\n"
635 " --disable-ssl-menu disallow changing transport mode\n",
636 user, supportsPAM() ? "'AUTH' | " : "");
641 static void destroyExternalFileHashEntry(void *arg, char *key, char *value) {
646 static void parseArgs(int argc, char * const argv[]) {
647 int hasSSL = serverSupportsSSL();
653 const char *pidfile = NULL;
654 int verbosity = MSG_DEFAULT;
655 externalFiles = newHashMap(destroyExternalFileHashEntry, NULL);
656 HashMap *serviceTable = newHashMap(destroyServiceHashEntry, NULL);
658 static const char optstring[] = "+hb::c:df:g:np:s:tqu:v";
659 static struct option options[] = {
660 { "help", 0, 0, 'h' },
661 { "background", 2, 0, 'b' },
662 { "cert", 1, 0, 'c' },
663 { "cert-fd", 1, 0, 0 },
665 { "debug", 0, 0, 'd' },
666 { "static-file", 1, 0, 'f' },
667 { "group", 1, 0, 'g' },
668 { "linkify", 1, 0, 0 },
669 { "localhost-only", 0, 0, 0 },
670 { "no-beep", 0, 0, 0 },
671 { "numeric", 0, 0, 'n' },
672 { "port", 1, 0, 'p' },
673 { "service", 1, 0, 's' },
674 { "disable-ssl", 0, 0, 't' },
675 { "disable-ssl-menu", 0, 0, 0 },
676 { "quiet", 0, 0, 'q' },
677 { "user", 1, 0, 'u' },
678 { "verbose", 0, 0, 'v' },
679 { "version", 0, 0, 0 },
682 int c = getopt_long(argc, argv, optstring, options, &idx);
684 for (int i = 0; options[i].name; i++) {
685 if (options[i].val == c) {
694 // Help (or invalid argument)
697 fatal("Failed to parse command line");
703 fatal("CGI and background operations are mutually exclusive");
706 if (optarg && pidfile) {
707 fatal("Only one pidfile can be given");
710 pidfile = strdup(optarg);
715 warn("Ignoring certificate directory, as SSL support is unavailable");
717 if (certificateFd >= 0) {
718 fatal("Cannot set both a certificate directory and file handle");
720 if (certificateDir) {
721 fatal("Only one certificate directory can be selected");
723 check(certificateDir = strdup(optarg));
725 // Certificate file descriptor
727 warn("Ignoring certificate directory, as SSL support is unavailable");
729 if (certificateDir) {
730 fatal("Cannot set both a certificate directory and file handle");
732 if (certificateFd >= 0) {
733 fatal("Only one certificate file handle can be provided");
735 int tmpFd = strtoint(optarg, 3, INT_MAX);
736 certificateFd = dup(tmpFd);
737 if (certificateFd < 0) {
738 fatal("Invalid certificate file handle");
740 check(!NOINTR(close(tmpFd)));
744 fatal("CGI and background operations are mutually exclusive");
747 fatal("Cannot specify a port for CGI operation");
751 char *ptr = strchr(optarg, '-');
753 fatal("Syntax error in port range specification");
756 portMin = strtoint(optarg, 1, 65535);
758 portMax = strtoint(ptr + 1, portMin, 65535);
762 if (!logIsDefault() && !logIsDebug()) {
763 fatal("--debug is mutually exclusive with --quiet and --verbose.");
765 verbosity = MSG_DEBUG;
766 logSetLogLevel(verbosity);
769 char *ptr, *path, *file;
770 if ((ptr = strchr(optarg, ':')) == NULL) {
771 fatal("Syntax error in static-file definition \"%s\".", optarg);
773 check(path = malloc(ptr - optarg + 1));
774 memcpy(path, optarg, ptr - optarg);
775 path[ptr - optarg] = '\000';
776 file = strdup(ptr + 1);
777 if (getRefFromHashMap(externalFiles, path)) {
778 fatal("Duplicate static-file definition for \"%s\".", path);
780 addToHashMap(externalFiles, path, file);
783 if (runAsGroup >= 0) {
784 fatal("Duplicate --group option.");
786 runAsGroup = parseGroup(optarg, NULL);
789 if (!strcmp(optarg, "none")) {
791 } else if (!strcmp(optarg, "normal")) {
793 } else if (!strcmp(optarg, "aggressive")) {
796 fatal("Invalid argument for --linkify. Must be "
797 "\"none\", \"normal\", or \"aggressive\".");
811 fatal("Duplicate --port option");
814 fatal("Cannot specifiy a port for CGI operation");
816 port = strtoint(optarg, 1, 65535);
819 struct Service *service;
820 service = newService(optarg);
821 if (getRefFromHashMap(serviceTable, service->path)) {
822 fatal("Duplicate service description for \"%s\".", service->path);
824 addToHashMap(serviceTable, service->path, (char *)service);
828 warn("Ignoring disable-ssl option, as SSL support is unavailable");
834 warn("Ignoring disable-ssl-menu option, as SSL support is "
840 if (!logIsDefault() && !logIsQuiet()) {
841 fatal("--quiet is mutually exclusive with --debug and --verbose.");
843 verbosity = MSG_QUIET;
844 logSetLogLevel(verbosity);
847 if (runAsUser >= 0) {
848 fatal("Duplicate --user option.");
850 runAsUser = parseUser(optarg, NULL);
853 if (!logIsDefault() && (!logIsInfo() || logIsDebug())) {
854 fatal("--verbose is mutually exclusive with --debug and --quiet");
856 verbosity = MSG_INFO;
857 logSetLogLevel(verbosity);
860 message("ShellInABox version " VERSION " (revision " VCS_REVISION ")");
864 if (optind != argc) {
866 fatal("Failed to parse command line");
870 for (int i = 0; i < argc; i++) {
871 buf = stringPrintf(buf, " %s", argv[i]);
873 info("Command line:%s", buf);
876 // If the user did not specify a port, use the default one
881 // If the user did not register any services, provide the default service
882 if (!getHashmapSize(serviceTable)) {
883 addToHashMap(serviceTable, "/", (char *)newService(":LOGIN"));
885 enumerateServices(serviceTable);
886 deleteHashMap(serviceTable);
888 // Do not allow non-root URLs for CGI operation
890 for (int i = 0; i < numServices; i++) {
891 if (strcmp(services[i]->path, "/")) {
892 fatal("Non-root service URLs are incompatible with CGI operation");
895 check(cgiSessionKey = newSessionKey());
900 check((pid = fork()) >= 0);
907 #define O_LARGEFILE 0
909 int fd = NOINTR(open(pidfile,
910 O_WRONLY|O_TRUNC|O_LARGEFILE|O_CREAT,
914 NOINTR(write(fd, buf, snprintf(buf, 40, "%d", (int)getpid())));
915 check(!NOINTR(close(fd)));
919 free((char *)pidfile);
922 static void removeLimits() {
923 static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
924 for (int i = 0; i < sizeof(res)/sizeof(int); i++) {
926 getrlimit(res[i], &rl);
927 if (rl.rlim_max < RLIM_INFINITY) {
928 rl.rlim_max = RLIM_INFINITY;
929 setrlimit(res[i], &rl);
930 getrlimit(res[i], &rl);
932 if (rl.rlim_cur < rl.rlim_max) {
933 rl.rlim_cur = rl.rlim_max;
934 setrlimit(res[i], &rl);
939 static void setUpSSL(Server *server) {
940 serverEnableSSL(server, enableSSL);
942 // Enable SSL support (if available)
944 check(serverSupportsSSL());
945 if (certificateFd >= 0) {
946 serverSetCertificateFd(server, certificateFd);
947 } else if (certificateDir) {
949 if (strchr(certificateDir, '%')) {
950 fatal("Invalid certificate directory name \"%s\".", certificateDir);
952 check(tmp = stringPrintf(NULL, "%s/certificate%%s.pem", certificateDir));
953 serverSetCertificate(server, tmp, 1);
956 serverSetCertificate(server, "certificate%s.pem", 1);
961 int main(int argc, char * const argv[]) {
962 #ifdef HAVE_SYS_PRCTL_H
963 // Disable core files
964 prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
966 struct rlimit rl = { 0 };
967 setrlimit(RLIMIT_CORE, &rl);
970 // Parse command line arguments
971 parseArgs(argc, argv);
973 // Fork the launcher process, allowing us to drop privileges in the main
975 int launcherFd = forkLauncher();
977 // Make sure that our timestamps will print in the standard format
978 setlocale(LC_TIME, "POSIX");
980 // Create a new web server
983 check(server = newServer(localhostOnly, port));
987 // For CGI operation we fork the new server, so that it runs in the
993 check((pid = fork()) >= 0);
995 // Wait for child to output initial HTML page
997 check(!NOINTR(close(fds[1])));
998 check(!NOINTR(read(fds[0], &wait, 1)));
999 check(!NOINTR(close(fds[0])));
1002 check(!NOINTR(close(fds[0])));
1003 check(server = newCGIServer(localhostOnly, portMin, portMax,
1008 // Output a <frameset> that includes our root page
1009 check(port = serverGetListeningPort(server));
1010 extern char cgiRootStart[];
1011 extern char cgiRootEnd[];
1013 check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1));
1014 memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart);
1015 cgiRoot[cgiRootEnd - cgiRootStart] = '\000';
1016 printf("X-ShellInABox-Port: %d\r\n"
1017 "X-ShellInABox-Pid: %d\r\n"
1018 "Content-type: text/html; charset=utf-8\r\n\r\n",
1020 printfUnchecked(cgiRoot, port, cgiSessionKey);
1023 check(!NOINTR(close(fds[1])));
1024 closeAllFds((int []){ launcherFd, serverGetFd(server) }, 2);
1025 logSetLogLevel(MSG_QUIET);
1028 // Set log file format
1029 serverSetNumericHosts(server, numericHosts);
1031 // Disable /quit handler
1032 serverRegisterHttpHandler(server, "/quit", NULL, NULL);
1034 // Register HTTP handler(s)
1035 for (int i = 0; i < numServices; i++) {
1036 serverRegisterHttpHandler(server, services[i]->path,
1037 shellInABoxHttpHandler, services[i]);
1040 // Register handlers for external files
1041 iterateOverHashMap(externalFiles, registerExternalFiles, server);
1047 deleteServer(server);
1048 finishAllSessions();
1049 deleteHashMap(externalFiles);
1050 for (int i = 0; i < numServices; i++) {
1051 deleteService(services[i]);
1054 free(certificateDir);
1055 free(cgiSessionKey);