"Fossies" - the Fresh Open Source Software Archive

Member "shellinabox-2.20/shellinabox/shellinaboxd.c" (9 Nov 2016, 54568 Bytes) of package /linux/privat/shellinabox-2.20.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "shellinaboxd.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.19_vs_2.20.

    1 // shellinaboxd.c -- A custom web server that makes command line applications
    2 //                   available as AJAX web applications.
    3 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
    4 //
    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.
    8 //
    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.
   13 //
   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.
   17 //
   18 // In addition to these license terms, the author grants the following
   19 // additional rights:
   20 //
   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.
   29 //
   30 // You may at your option choose to remove this additional permission from
   31 // the work, or from any part of it.
   32 //
   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:
   36 //
   37 // This product includes software developed by the OpenSSL Project
   38 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
   39 //
   40 // This product includes cryptographic software written by Eric Young
   41 // (eay@cryptsoft.com)
   42 //
   43 //
   44 // The most up-to-date version of this program is always available from
   45 // http://shellinabox.com
   46 
   47 #define _GNU_SOURCE
   48 #include "config.h"
   49 
   50 #include <fcntl.h>
   51 #include <getopt.h>
   52 #include <limits.h>
   53 #include <locale.h>
   54 #include <poll.h>
   55 #include <setjmp.h>
   56 #include <signal.h>
   57 #include <stdarg.h>
   58 #include <stdio.h>
   59 #include <stdlib.h>
   60 #include <string.h>
   61 #include <sys/resource.h>
   62 #include <sys/types.h>
   63 #include <sys/stat.h>
   64 #include <sys/socket.h>
   65 #include <sys/un.h>
   66 #include <time.h>
   67 #include <unistd.h>
   68 
   69 #ifdef HAVE_SYS_PRCTL_H
   70 #include <sys/prctl.h>
   71 #endif
   72 
   73 #include "libhttp/http.h"
   74 #include "libhttp/server.h"
   75 #include "logging/logging.h"
   76 #include "shellinabox/externalfile.h"
   77 #include "shellinabox/launcher.h"
   78 #include "shellinabox/privileges.h"
   79 #include "shellinabox/service.h"
   80 #include "shellinabox/session.h"
   81 #include "shellinabox/usercss.h"
   82 
   83 #ifdef HAVE_UNUSED
   84 #defined ATTR_UNUSED __attribute__((unused))
   85 #defined UNUSED(x)   do { } while (0)
   86 #else
   87 #define ATTR_UNUSED
   88 #define UNUSED(x)    do { (void)(x); } while (0)
   89 #endif
   90 
   91 // Embedded resources
   92 #include "shellinabox/beep.h"
   93 #include "shellinabox/cgi_root.h"
   94 #include "shellinabox/enabled.h"
   95 #include "shellinabox/favicon.h"
   96 #include "shellinabox/keyboard.h"
   97 #include "shellinabox/keyboard-layout.h"
   98 #include "shellinabox/print-styles.h"
   99 #include "shellinabox/root_page.h"
  100 #include "shellinabox/shell_in_a_box.h"
  101 #include "shellinabox/styles.h"
  102 #include "shellinabox/vt100.h"
  103 
  104 #define PORTNUM           4200
  105 #define MAX_RESPONSE      2048
  106 
  107 static int            port;
  108 static int            portMin;
  109 static int            portMax;
  110 static int            localhostOnly     = 0;
  111 static int            noBeep            = 0;
  112 static int            numericHosts      = 0;
  113 static int            peerCheckEnabled  = 1;
  114 static int            enableSSL         = 1;
  115 static int            enableSSLMenu     = 1;
  116 static int            forceSSL          = 1; // TODO enable http fallback with commandline option
  117 int                   enableUtmpLogging = 1;
  118 static char           *messagesOrigin   = NULL;
  119 static int            linkifyURLs       = 1;
  120 static char           *certificateDir;
  121 static int            certificateFd     = -1;
  122 static HashMap        *externalFiles;
  123 static Server         *cgiServer;
  124 static char           *cgiSessionKey;
  125 static int            cgiSessions;
  126 static char           *cssStyleSheet;
  127 static struct UserCSS *userCSSList;
  128 static const char     *pidfile;
  129 static sigjmp_buf     jmpenv;
  130 static volatile int   exiting;
  131 
  132 static char *jsonEscape(const char *buf, int len) {
  133   static const char *hexDigit = "0123456789ABCDEF";
  134 
  135   // Determine the space that is needed to encode the buffer
  136   int count                   = 0;
  137   const char *ptr             = buf;
  138   for (int i = 0; i < len; i++) {
  139     unsigned char ch          = *(unsigned char *)ptr++;
  140     if (ch < ' ') {
  141       switch (ch) {
  142       case '\b': case '\f': case '\n': case '\r': case '\t':
  143         count                += 2;
  144         break;
  145       default:
  146         count                += 6;
  147         break;
  148       }
  149     } else if (ch == '"' || ch == '\\' || ch == '/') {
  150       count                  += 2;
  151     } else if (ch > '\x7F') {
  152       count                  += 6;
  153     } else {
  154       count++;
  155     }
  156   }
  157 
  158   // Encode the buffer using JSON string escaping
  159   char *result;
  160   check(result                = malloc(count + 1));
  161   char *dst                   = result;
  162   ptr                         = buf;
  163   for (int i = 0; i < len; i++) {
  164     unsigned char ch          = *(unsigned char *)ptr++;
  165     if (ch < ' ') {
  166       *dst++                  = '\\';
  167       switch (ch) {
  168       case '\b': *dst++       = 'b'; break;
  169       case '\f': *dst++       = 'f'; break;
  170       case '\n': *dst++       = 'n'; break;
  171       case '\r': *dst++       = 'r'; break;
  172       case '\t': *dst++       = 't'; break;
  173       default:
  174       unicode:
  175         *dst++                = 'u';
  176         *dst++                = '0';
  177         *dst++                = '0';
  178         *dst++                = hexDigit[ch >> 4];
  179         *dst++                = hexDigit[ch & 0xF];
  180         break;
  181       }
  182     } else if (ch == '"' || ch == '\\' || ch == '/') {
  183       *dst++                  = '\\';
  184       *dst++                  = ch;
  185     } else if (ch > '\x7F') {
  186       *dst++                  = '\\';
  187       goto unicode;
  188     } else {
  189       *dst++                  = ch;
  190     }
  191   }
  192   *dst++                      = '\000';
  193   return result;
  194 }
  195 
  196 static int printfUnchecked(const char *format, ...) {
  197   // Some Linux distributions enable -Wformat=2 by default. This is a
  198   // very unfortunate decision, as that option generates a lot of false
  199   // positives. We try to work around the problem by defining an unchecked
  200   // version of "printf()"
  201   va_list ap;
  202   va_start(ap, format);
  203   int rc = vprintf(format, ap);
  204   va_end(ap);
  205   return rc;
  206 }
  207 
  208 static int completePendingRequest(struct Session *session,
  209                                   const char *buf, int len, int maxLength) {
  210   // If there is no pending HTTP request, save the data and return
  211   // immediately.
  212   if (!session->http) {
  213     if (len) {
  214       if (session->buffered) {
  215         check(session->buffered = realloc(session->buffered,
  216                                           session->len + len));
  217         memcpy(session->buffered + session->len, buf, len);
  218         session->len           += len;
  219       } else {
  220         check(session->buffered = malloc(len));
  221         memcpy(session->buffered, buf, len);
  222         session->len            = len;
  223       }
  224     }
  225   } else {
  226     // If we have a pending HTTP request, we can reply to it, now.
  227     char *data;
  228     if (session->buffered) {
  229       check(session->buffered   = realloc(session->buffered,
  230                                           session->len + len));
  231       memcpy(session->buffered + session->len, buf, len);
  232       session->len             += len;
  233       if (maxLength > 0 && session->len > maxLength) {
  234         data                    = jsonEscape(session->buffered, maxLength);
  235         session->len           -= maxLength;
  236         memmove(session->buffered, session->buffered + maxLength,
  237                 session->len);
  238       } else {
  239         data                    = jsonEscape(session->buffered, session->len);
  240         free(session->buffered);
  241         session->buffered       = NULL;
  242         session->len            = 0;
  243       }
  244     } else {
  245       if (maxLength > 0 && len > maxLength) {
  246         session->len            = len - maxLength;
  247         check(session->buffered = malloc(session->len));
  248         memcpy(session->buffered, buf + maxLength, session->len);
  249         data                    = jsonEscape(buf, maxLength);
  250       } else {
  251         data                    = jsonEscape(buf, len);
  252       }
  253     }
  254 
  255     char *json                  = stringPrintf(NULL, "{"
  256                                                "\"session\":\"%s\","
  257                                                "\"data\":\"%s\""
  258                                                "}",
  259                                                session->sessionKey, data);
  260     free(data);
  261     HttpConnection *http        = session->http;
  262     char *response              = stringPrintf(NULL,
  263                                              "HTTP/1.1 200 OK\r\n"
  264                                              "Content-Type: application/json; "
  265                                              "charset=utf-8\r\n"
  266                                              "Content-Length: %ld\r\n"
  267                                              "Cache-Control: no-cache\r\n"
  268                                              "\r\n"
  269                                              "%s",
  270                                              (long)strlen(json),
  271                                              strcmp(httpGetMethod(http),
  272                                                     "HEAD") ? json : "");
  273     free(json);
  274     session->http               = NULL;
  275     httpTransfer(http, response, strlen(response));
  276   }
  277   if (session->done && !session->buffered) {
  278     finishSession(session);
  279     return 0;
  280   }
  281   return 1;
  282 }
  283 
  284 static void sessionDone(void *arg) {
  285   struct Session *session = (struct Session *)arg;
  286   debug("[server] Session %s done.", session->sessionKey);
  287   if (session->cleanup) {
  288     terminateChild(session);
  289   }
  290   session->done           = 1;
  291   addToGraveyard(session);
  292   completePendingRequest(session, "", 0, INT_MAX);
  293 }
  294 
  295 static void delaySession(void) {
  296   struct timespec ts;
  297   ts.tv_sec              = 0;
  298   ts.tv_nsec             = 200 * 1000; // Delay for 0.2 ms
  299   nanosleep(&ts, NULL);
  300 }
  301 
  302 static int handleSession(struct ServerConnection *connection, void *arg,
  303                          short *events, short revents) {
  304   struct Session *session       = (struct Session *)arg;
  305   session->connection           = connection;
  306   int len                       = MAX_RESPONSE - session->len;
  307   if (len <= 0) {
  308     len                         = 1;
  309   }
  310   char buf[MAX_RESPONSE];
  311   int bytes                     = 0;
  312   if (revents & POLLIN) {
  313     bytes                       = NOINTR(read(session->pty, buf, len));
  314     if (bytes <= 0) {
  315       return 0;
  316     }
  317   }
  318   int timedOut                  = serverGetTimeout(connection) < 0;
  319   if (bytes || timedOut) {
  320     if (!session->http && timedOut) {
  321       debug("[server] Timeout. Closing session %s!", session->sessionKey);
  322       session->cleanup = 1;
  323       return 0;
  324     }
  325     check(!session->done);
  326     check(completePendingRequest(session, buf, bytes, MAX_RESPONSE));
  327     connection                  = serverGetConnection(session->server,
  328                                                       connection,
  329                                                       session->pty);
  330     session->connection         = connection;
  331     if (session->len >= MAX_RESPONSE) {
  332       *events                   = 0;
  333     }
  334     serverSetTimeout(connection, AJAX_TIMEOUT);
  335     session->ptyFirstRead       = 0;
  336     return 1;
  337   } else {
  338     if (revents & POLLHUP) {
  339       if (session->useLogin && session->ptyFirstRead) {
  340         // Workaround for random "Session closed" issues related to /bin/login
  341         // closing and reopening our pty during initialization. This happens only
  342         // on some systems like Fedora for example.
  343         // Here we allow that our pty is closed by ignoring POLLHUP on first read.
  344         // Delay is also needed so that login process has some time to reopen pty.
  345         // Note that the issue may occur anyway but with workaround we reduce the
  346         // chances.
  347         debug("[server] POLLHUP received on login PTY first read!");
  348         session->ptyFirstRead   = 0;
  349         delaySession();
  350         return 1;
  351       }
  352       debug("[server] POLLHUP received on PTY! Closing session %s!",
  353             session->sessionKey);
  354     }
  355     return 0;
  356   }
  357 }
  358 
  359 static int invalidatePendingHttpSession(void *arg, const char *key,
  360                                         char **value) {
  361   struct Session *session = *(struct Session **)value;
  362   if (session->http && session->http == (HttpConnection *)arg) {
  363     debug("[server] Clearing pending HTTP connection for session %s!", key);
  364     session->http         = NULL;
  365     serverDeleteConnection(session->server, session->pty);
  366 
  367     // Return zero in order to remove this HTTP from the "session" hashmap
  368     return 0;
  369   }
  370 
  371   // If the session is still in use, do not remove it from the "sessions" map
  372   return 1;
  373 }
  374 
  375 static int dataHandler(HttpConnection *http, struct Service *service,
  376                        const char *buf, int len ATTR_UNUSED, URL *url) {
  377   UNUSED(len);
  378   if (!buf) {
  379     // Somebody unexpectedly closed our http connection (e.g. because of a
  380     // timeout). This is the last notification that we will get.
  381     iterateOverSessions(invalidatePendingHttpSession, http);
  382     return HTTP_DONE;
  383   }
  384 
  385   // Find an existing session, or create the record for a new one
  386   const HashMap *args     = urlGetArgs(url);
  387   const char *sessionKey  = getFromHashMap(args, "session");
  388 
  389   int sessionIsNew;
  390   struct Session *session = findSession(sessionKey, cgiSessionKey, &sessionIsNew, http);
  391   if (session == NULL) {
  392     httpSendReply(http, 400, "Bad Request", NO_MSG);
  393     return HTTP_DONE;
  394   }
  395 
  396   // Sanity check
  397   if (!sessionIsNew && peerCheckEnabled && strcmp(session->peerName, httpGetPeerName(http))) {
  398     error("[server] Peername changed from %s to %s",
  399           session->peerName, httpGetPeerName(http));
  400     httpSendReply(http, 400, "Bad Request", NO_MSG);
  401     return HTTP_DONE;
  402   }
  403 
  404   int oldWidth            = session->width;
  405   int oldHeight           = session->height;
  406   const char *width       = getFromHashMap(args, "width");
  407   const char *height      = getFromHashMap(args, "height");
  408   const char *keys        = getFromHashMap(args, "keys");
  409   const char *rootURL     = getFromHashMap(args, "rooturl");
  410 
  411   // Adjust window dimensions if provided by client
  412   if (width && height) {
  413     session->width        = atoi(width);
  414     session->height       = atoi(height);
  415   }
  416 
  417   // Create a new session, if the client did not provide an existing one
  418   if (sessionIsNew) {
  419     if (keys) {
  420     bad_new_session:
  421       abandonSession(session);
  422       httpSendReply(http, 400, "Bad Request", NO_MSG);
  423       return HTTP_DONE;
  424     }
  425 
  426     if (cgiServer && cgiSessions++) {
  427       serverExitLoop(cgiServer, 1);
  428       goto bad_new_session;
  429     }
  430     session->http         = http;
  431     session->useLogin     = service->useLogin;
  432     if (launchChild(service->id, session,
  433                     rootURL && *rootURL ? rootURL : urlGetURL(url)) < 0) {
  434       abandonSession(session);
  435       httpSendReply(http, 500, "Internal Error", NO_MSG);
  436       return HTTP_DONE;
  437     }
  438     if (cgiServer) {
  439       terminateLauncher();
  440     }
  441     session->connection   = serverAddConnection(httpGetServer(http),
  442                                                 session->pty, handleSession,
  443                                                 sessionDone, session);
  444     serverSetTimeout(session->connection, AJAX_TIMEOUT);
  445   }
  446 
  447   // Reset window dimensions of the pseudo TTY, if changed since last time set.
  448   if (session->width > 0 && session->height > 0 &&
  449       (session->width != oldWidth || session->height != oldHeight)) {
  450     debug("[server] Window size changed to %dx%d", session->width,
  451           session->height);
  452     setWindowSize(session->pty, session->width, session->height);
  453   }
  454 
  455   // Process keypresses, if any. Then send a synchronous reply.
  456   if (keys) {
  457     char *keyCodes;
  458     check(keyCodes        = malloc(strlen(keys)/2));
  459     int len               = 0;
  460     for (const unsigned char *ptr = (const unsigned char *)keys; ;) {
  461       unsigned c0         = *ptr++;
  462       if (c0 < '0' || (c0 > '9' && c0 < 'A') ||
  463           (c0 > 'F' && c0 < 'a') || c0 > 'f') {
  464         break;
  465       }
  466       unsigned c1         = *ptr++;
  467       if (c1 < '0' || (c1 > '9' && c1 < 'A') ||
  468           (c1 > 'F' && c1 < 'a') || c1 > 'f') {
  469         break;
  470       }
  471       keyCodes[len++]     = 16*((c0 & 0xF) + 9*(c0 > '9')) +
  472                                 (c1 & 0xF) + 9*(c1 > '9');
  473     }
  474     if (write(session->pty, keyCodes, len) < 0 && errno == EAGAIN) {
  475       completePendingRequest(session, "\007", 1, MAX_RESPONSE);
  476     }
  477     free(keyCodes);
  478     httpSendReply(http, 200, "OK", " ");
  479     check(session->http != http);
  480     return HTTP_DONE;
  481   } else {
  482     // This request is polling for data. Finish any pending requests and
  483     // queue (or process) a new one.
  484     if (session->http && session->http != http &&
  485         !completePendingRequest(session, "", 0, MAX_RESPONSE)) {
  486       httpSendReply(http, 400, "Bad Request", NO_MSG);
  487       return HTTP_DONE;
  488     }
  489     session->http         = http;
  490   }
  491 
  492   session->connection     = serverGetConnection(session->server,
  493                                                 session->connection,
  494                                                 session->pty);
  495   if (session->buffered || sessionIsNew) {
  496     if (completePendingRequest(session, "", 0, MAX_RESPONSE) &&
  497         session->connection) {
  498       // Reset the timeout, as we just received a new request.
  499       serverSetTimeout(session->connection, AJAX_TIMEOUT);
  500       if (session->len < MAX_RESPONSE) {
  501         // Re-enable input on the child's pty
  502         serverConnectionSetEvents(session->server, session->connection,
  503                                   session->pty, POLLIN);
  504       }
  505     }
  506     return HTTP_DONE;
  507   } else if (session->connection) {
  508     // Re-enable input on the child's pty
  509     serverConnectionSetEvents(session->server, session->connection,
  510                               session->pty, POLLIN);
  511     serverSetTimeout(session->connection, AJAX_TIMEOUT);
  512   }
  513 
  514   return HTTP_SUSPEND;
  515 }
  516 
  517 static void serveStaticFile(HttpConnection *http, const char *contentType,
  518                             const char *start, const char *end) {
  519   char *body                     = (char *)start;
  520   char *bodyEnd                  = (char *)end;
  521 
  522   // Unfortunately, there are still some browsers that are so buggy that they
  523   // need special conditional code. In anything that has a "text" MIME type,
  524   // we allow simple conditionals. Nested conditionals are not supported.
  525   if (!memcmp(contentType, "text/", 5)) {
  526     char *tag                    = NULL;
  527     int condTrue                 = -1;
  528     char *ifPtr                  = NULL;
  529     char *elsePtr                = NULL;
  530     for (char *ptr = body; bodyEnd - ptr >= 6; ) {
  531       char *eol                  = ptr;
  532       eol                        = memchr(eol, '\n', bodyEnd - eol);
  533       if (eol == NULL) {
  534         eol                      = bodyEnd;
  535       } else {
  536         ++eol;
  537       }
  538       if (!memcmp(ptr, "[if ", 4)) {
  539         char *bracket            = memchr(ptr + 4, ']', eol - ptr - 4);
  540         if (bracket != NULL && bracket > ptr + 4) {
  541           if (tag != NULL) {
  542             free(tag);
  543           }
  544           check(tag              = malloc(bracket - ptr - 3));
  545           memcpy(tag, ptr + 4, bracket - ptr - 4);
  546           tag[bracket - ptr - 4] = '\000';
  547           condTrue               = 0;
  548           const char *userAgent  = getFromHashMap(httpGetHeaders(http),
  549                                                   "user-agent");
  550           if (!userAgent) {
  551             userAgent            = "";
  552           }
  553 
  554           // Allow multiple comma separated conditions. Conditions are either
  555           // substrings found in the user agent, or they are "DEFINES_..."
  556           // tags at the top of user CSS files.
  557           for (char *tagPtr = tag; *tagPtr; ) {
  558             char *e              = strchr(tagPtr, ',');
  559             if (!e) {
  560               e                  = strchr(tag, '\000');
  561             } else {
  562               *e++               = '\000';
  563             }
  564             condTrue             = userCSSGetDefine(tagPtr) ||
  565                                    strstr(userAgent, tagPtr) != NULL;
  566             if (*e) {
  567               e[-1]              = ',';
  568             }
  569             if (condTrue) {
  570               break;
  571             }
  572             tagPtr               = e;
  573           }
  574 
  575           // If we find any conditionals, then we need to make a copy of
  576           // the text document. We do this lazily, as presumably the majority
  577           // of text documents won't have conditionals.
  578           if (body == start) {
  579             check(body           = malloc(end - start));
  580             memcpy(body, start, end - start);
  581             bodyEnd             += body - start;
  582             ptr                 += body - start;
  583             eol                 += body - start;
  584           }
  585 
  586           // Remember the beginning of the "[if ...]" statement
  587           ifPtr                  = ptr;
  588         }
  589       } else if (ifPtr && !elsePtr && eol - ptr >= (ssize_t)strlen(tag) + 7 &&
  590                  !memcmp(ptr, "[else ", 6) &&
  591                  !memcmp(ptr + 6, tag, strlen(tag)) &&
  592                  ptr[6 + strlen(tag)] == ']') {
  593         // Found an "[else ...]" statement. Remember where it started.
  594         elsePtr                  = ptr;
  595       } else if (ifPtr && eol - ptr >= (ssize_t)strlen(tag) + 8 &&
  596                  !memcmp(ptr, "[endif ", 7) &&
  597                  !memcmp(ptr + 7, tag, strlen(tag)) &&
  598                  ptr[7 + strlen(tag)] == ']') {
  599         // Found the closing "[endif ...]" statement. Now we can remove those
  600         // parts of the conditionals that do not apply to this user agent.
  601         char *s, *e;
  602         if (condTrue) {
  603           s                      = strchr(ifPtr, '\n') + 1;
  604           e                      = elsePtr ? elsePtr : ptr;
  605         } else {
  606           if (elsePtr) {
  607             s                    = strchr(elsePtr, '\n') + 1;
  608             e                    = ptr;
  609           } else {
  610             s                    = ifPtr;
  611             e                    = ifPtr;
  612           }
  613         }
  614         memmove(ifPtr, s, e - s);
  615         memmove(ifPtr + (e - s), eol, bodyEnd - eol);
  616         bodyEnd                 -= (s - ifPtr) + (eol - e);
  617         eol                      = ifPtr + (e - s);
  618         ifPtr                    = NULL;
  619         elsePtr                  = NULL;
  620         free(tag);
  621         tag                      = NULL;
  622       }
  623       ptr                        = eol;
  624     }
  625     free(tag);
  626   }
  627 
  628   char *response   = stringPrintf(NULL,
  629                                   "HTTP/1.1 200 OK\r\n"
  630                                   "Content-Type: %s\r\n"
  631                                   "Content-Length: %ld\r\n"
  632                                   "%s\r\n",
  633                                   contentType, (long)(bodyEnd - body),
  634                                   body == start ? "" :
  635                                   "Cache-Control: no-cache\r\n");
  636   int len          = strlen(response);
  637   if (strcmp(httpGetMethod(http), "HEAD")) {
  638     check(response = realloc(response, len + (bodyEnd - body)));
  639     memcpy(response + len, body, bodyEnd - body);
  640     len           += bodyEnd - body;
  641   }
  642 
  643   // If we expanded conditionals, we had to create a temporary copy. Delete
  644   // it now.
  645   if (body != start) {
  646     free(body);
  647   }
  648 
  649   httpTransfer(http, response, len);
  650 }
  651 
  652 static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
  653                                   const char *buf, int len) {
  654   checkGraveyard();
  655   URL *url                = newURL(http, buf, len);
  656   const HashMap *headers  = httpGetHeaders(http);
  657   const char *contentType = getFromHashMap(headers, "content-type");
  658 
  659   // Normalize the path info, present the final path element
  660   const char *pathInfo    = urlGetPathInfo(url);
  661   int pathInfoLength = 0;
  662   pathInfo = strrchr (pathInfo, '/');
  663   if (pathInfo) {
  664     ++pathInfo;
  665   } else {
  666     pathInfo = "";              // Cheap way to get an empty string
  667   }
  668   pathInfoLength = strlen (pathInfo);
  669 
  670   if (!pathInfoLength ||
  671       (pathInfoLength == 5 && !memcmp(pathInfo, "plain", 5)) ||
  672       (pathInfoLength == 6 && !memcmp(pathInfo, "secure", 6))) {
  673     // The root page serves the AJAX application.
  674     if (contentType &&
  675         !strncasecmp(contentType, "application/x-www-form-urlencoded", 33)) {
  676       // XMLHttpRequest carrying data between the AJAX application and the
  677       // client session.
  678       int status          = dataHandler(http, arg, buf, len, url);
  679       deleteURL(url);
  680       return status;
  681     }
  682     UNUSED(rootPageSize);
  683     char *html            = stringPrintf(NULL, rootPageStart,
  684                                          enableSSL ? "true" : "false");
  685     serveStaticFile(http, "text/html", html, strrchr(html, '\000'));
  686     free(html);
  687   } else if (pathInfoLength == 8 && !memcmp(pathInfo, "beep.wav", 8)) {
  688     // Serve the audio sample for the console bell.
  689     serveStaticFile(http, "audio/x-wav", beepStart, beepStart + beepSize - 1);
  690   } else if (pathInfoLength == 11 && !memcmp(pathInfo, "enabled.gif", 11)) {
  691     // Serve the checkmark icon used in the context menu
  692     serveStaticFile(http, "image/gif", enabledStart,
  693                     enabledStart + enabledSize - 1);
  694   } else if (pathInfoLength == 11 && !memcmp(pathInfo, "favicon.ico", 11)) {
  695     // Serve the favicon
  696     serveStaticFile(http, "image/x-icon", faviconStart,
  697                     faviconStart + faviconSize - 1);
  698   } else if (pathInfoLength == 13 && !memcmp(pathInfo, "keyboard.html", 13)) {
  699     // Serve the keyboard layout
  700     serveStaticFile(http, "text/html", keyboardLayoutStart,
  701                     keyboardLayoutStart + keyboardLayoutSize - 1);
  702   } else if (pathInfoLength == 12 && !memcmp(pathInfo, "keyboard.png", 12)) {
  703     // Serve the keyboard icon
  704     serveStaticFile(http, "image/png", keyboardStart,
  705                     keyboardStart + keyboardSize - 1);
  706   } else if (pathInfoLength == 14 && !memcmp(pathInfo, "ShellInABox.js", 14)) {
  707     // Serve both vt100.js and shell_in_a_box.js in the same transaction.
  708     // Also, indicate to the client whether the server is SSL enabled.
  709     char *userCSSString   = getUserCSSString(userCSSList);
  710     char *stateVars       = stringPrintf(NULL,
  711                                          "serverSupportsSSL = %s;\n"
  712                                          "disableSSLMenu    = %s;\n"
  713                                          "suppressAllAudio  = %s;\n"
  714                                          "linkifyURLs       = %d;\n"
  715                                          "userCSSList       = %s;\n"
  716                                          "serverMessagesOrigin = %s%s%s;\n\n",
  717                                          enableSSL      ? "true" : "false",
  718                                          !enableSSLMenu ? "true" : "false",
  719                                          noBeep         ? "true" : "false",
  720                                          linkifyURLs,
  721                                          userCSSString,
  722                                          messagesOrigin ? "'" : "",
  723                                          messagesOrigin ? messagesOrigin : "false",
  724                                          messagesOrigin ? "'" : "");
  725     free(userCSSString);
  726     int stateVarsLength   = strlen(stateVars);
  727     int contentLength     = stateVarsLength +
  728                             vt100Size - 1 +
  729                             shellInABoxSize - 1;
  730     char *response        = stringPrintf(NULL,
  731                              "HTTP/1.1 200 OK\r\n"
  732                              "Content-Type: text/javascript; charset=utf-8\r\n"
  733                              "Content-Length: %d\r\n"
  734                              "\r\n",
  735                              contentLength);
  736     int headerLength      = strlen(response);
  737     if (strcmp(httpGetMethod(http), "HEAD")) {
  738       check(response      = realloc(response, headerLength + contentLength));
  739       memcpy(memcpy(memcpy(
  740           response + headerLength, stateVars, stateVarsLength)+stateVarsLength,
  741         vt100Start, vt100Size - 1) + vt100Size - 1,
  742       shellInABoxStart, shellInABoxSize - 1);
  743     } else {
  744       contentLength       = 0;
  745     }
  746     free(stateVars);
  747     httpTransfer(http, response, headerLength + contentLength);
  748   } else if (pathInfoLength == 10 && !memcmp(pathInfo, "styles.css", 10)) {
  749     // Serve the style sheet.
  750     serveStaticFile(http, "text/css; charset=utf-8",
  751                     cssStyleSheet, strrchr(cssStyleSheet, '\000'));
  752   } else if (pathInfoLength == 16 && !memcmp(pathInfo, "print-styles.css",16)){
  753     // Serve the style sheet.
  754     serveStaticFile(http, "text/css; charset=utf-8",
  755                     printStylesStart, printStylesStart + printStylesSize - 1);
  756   } else if (pathInfoLength > 8 && !memcmp(pathInfo, "usercss-", 8)) {
  757     // Server user style sheets (if any)
  758     struct UserCSS *css   = userCSSList;
  759     for (int idx          = atoi(pathInfo + 8);
  760          idx-- > 0 && css; css = css->next ) {
  761     }
  762     if (css) {
  763       serveStaticFile(http, "text/css; charset=utf-8",
  764                       css->style, css->style + css->styleLen);
  765     } else {
  766       httpSendReply(http, 404, "File not found", NO_MSG);
  767     }
  768   } else {
  769     httpSendReply(http, 404, "File not found", NO_MSG);
  770   }
  771 
  772   deleteURL(url);
  773   return HTTP_DONE;
  774 }
  775 
  776 static int strtoint(const char *s, int minVal, int maxVal) {
  777   char *ptr;
  778   if (!*s) {
  779     fatal("[config] Missing numeric value!");
  780   }
  781   long l = strtol(s, &ptr, 10);
  782   if (*ptr || l < minVal || l > maxVal) {
  783     fatal("[config] Range error on numeric value \"%s\"!", s);
  784   }
  785   return l;
  786 }
  787 
  788 static void usage(void) {
  789   // Drop privileges so that we can tell which uid/gid we would normally
  790   // run at.
  791   dropPrivileges();
  792   uid_t r_uid, e_uid, s_uid;
  793   uid_t r_gid, e_gid, s_gid;
  794   check(!getresuid(&r_uid, &e_uid, &s_uid));
  795   check(!getresgid(&r_gid, &e_gid, &s_gid));
  796   const char *user  = getUserName(r_uid);
  797   const char *group = getGroupName(r_gid);
  798 
  799   printf("Usage: shellinaboxd [OPTIONS]...\n"
  800           "Starts an HTTP server that serves terminal emulators to AJAX "
  801           "enabled browsers.\n"
  802           "\n"
  803           "List of command line options:\n"
  804           "  -b, --background[=PIDFILE]  run in background\n"
  805           "%s"
  806           "      --css=FILE              attach contents to CSS style sheet\n"
  807           "      --cgi[=PORTMIN-PORTMAX] run as CGI\n"
  808           "  -d, --debug                 enable debug mode\n"
  809           "  -f, --static-file=URL:FILE  serve static file from URL path\n"
  810           "  -g, --group=GID             switch to this group (default: %s)\n"
  811           "  -h, --help                  print this message\n"
  812           "      --linkify=[none|normal|aggressive] default is \"normal\"\n"
  813           "      --localhost-only        only listen on 127.0.0.1\n"
  814           "      --no-beep               suppress all audio output\n"
  815           "  -n, --numeric               do not resolve hostnames\n"
  816           "  -m, --messages-origin=ORIGIN allow iframe message passing from origin\n"
  817           "      --pidfile=PIDFILE       publish pid of daemon process\n"
  818           "  -p, --port=PORT             select a port (default: %d)\n"
  819           "  -s, --service=SERVICE       define one or more services\n"
  820           "%s"
  821           "      --disable-utmp-logging  disable logging to utmp and wtmp\n"
  822           "  -q, --quiet                 turn off all messages\n"
  823           "      --unixdomain-only=PATH:USER:GROUP:CHMOD listen on unix socket\n"
  824           "  -u, --user=UID              switch to this user (default: %s)\n"
  825           "      --user-css=STYLES       defines user-selectable CSS options\n"
  826           "  -v, --verbose               enable logging messages\n"
  827           "      --version               prints version information\n"
  828           "      --disable-peer-check    disable peer check on a session\n"
  829           "\n"
  830           "Debug, quiet, and verbose are mutually exclusive.\n"
  831           "\n"
  832           "One or more --service arguments define services that should "
  833           "be made available\n"
  834           "through the web interface:\n"
  835           "  SERVICE := <url-path> ':' APP\n"
  836           "  APP     := "
  837 #ifdef HAVE_BIN_LOGIN
  838                         "'LOGIN' | "
  839 #endif
  840                                    "'SSH' [ : <host> ] | "
  841                         "USER ':' CWD ':' CMD\n"
  842           "  USER    := %s<username> ':' <groupname>\n"
  843           "  CWD     := 'HOME' | <dir>\n"
  844           "  CMD     := 'SHELL' | <cmdline>\n"
  845           "\n"
  846           "<cmdline> supports variable expansion:\n"
  847           "  ${columns} - number of columns\n"
  848           "  ${gid}     - gid id\n"
  849           "  ${group}   - group name\n"
  850           "  ${home}    - home directory\n"
  851           "  ${lines}   - number of rows\n"
  852           "  ${peer}    - name of remote peer\n"
  853           "  ${realip}  - value of HTTP header field 'X-Real-IP'\n"
  854           "  ${uid}     - user id\n"
  855           "  ${url}     - the URL that serves the terminal session\n"
  856           "  ${user}    - user name\n"
  857           "\n"
  858           "One or more --user-css arguments define optional user-selectable "
  859           "CSS options.\n"
  860           "These options show up in the right-click context menu:\n"
  861           "  STYLES  := GROUP { ';' GROUP }*\n"
  862           "  GROUP   := OPTION { ',' OPTION }*\n"
  863           "  OPTION  := <label> ':' [ '-' | '+' ] <css-file>\n"
  864           "\n"
  865           "OPTIONs that make up a GROUP are mutually exclusive. But "
  866           "individual GROUPs are\n"
  867           "independent of each other.\n"
  868           "\n",
  869           !serverSupportsSSL() ? "" :
  870           "  -c, --cert=CERTDIR          set certificate dir "
  871           "(default: $PWD)\n"
  872           "      --cert-fd=FD            set certificate file from fd\n",
  873           group, PORTNUM,
  874           !serverSupportsSSL() ? "" :
  875           "  -t, --disable-ssl           disable transparent SSL support\n"
  876           "      --disable-ssl-menu      disallow changing transport mode\n",
  877           user, supportsPAM() ? "'AUTH' | " : "");
  878   free((char *)user);
  879   free((char *)group);
  880 }
  881 
  882 static void destroyExternalFileHashEntry(void *arg ATTR_UNUSED, char *key,
  883                                          char *value) {
  884   UNUSED(arg);
  885   free(key);
  886   free(value);
  887 }
  888 
  889 static void sigHandler(int signo, siginfo_t *info, void *context) {
  890   if (exiting++) {
  891     _exit(1);
  892   }
  893   siglongjmp(jmpenv, 1);
  894 }
  895 
  896 static void parseArgs(int argc, char * const argv[]) {
  897   int hasSSL               = serverSupportsSSL();
  898   if (!hasSSL) {
  899     enableSSL              = 0;
  900     forceSSL               = 0;
  901   }
  902   int demonize             = 0;
  903   int cgi                  = 0;
  904   int verbosity            = MSG_DEFAULT;
  905   externalFiles            = newHashMap(destroyExternalFileHashEntry, NULL);
  906   HashMap *serviceTable    = newHashMap(destroyServiceHashEntry, NULL);
  907   UNUSED(stylesSize);
  908   check(cssStyleSheet      = strdup(stylesStart));
  909 
  910   for (;;) {
  911     static const char optstring[] = "+hb::c:df:g:nm:p:s:tqu:v";
  912     static struct option options[] = {
  913       { "help",                 0, 0, 'h' },
  914       { "background",           2, 0, 'b' },
  915       { "cert",                 1, 0, 'c' },
  916       { "cert-fd",              1, 0,  0  },
  917       { "css",                  1, 0,  0  },
  918       { "cgi",                  2, 0,  0  },
  919       { "debug",                0, 0, 'd' },
  920       { "static-file",          1, 0, 'f' },
  921       { "group",                1, 0, 'g' },
  922       { "linkify",              1, 0,  0  },
  923       { "localhost-only",       0, 0,  0  },
  924       { "no-beep",              0, 0,  0  },
  925       { "numeric",              0, 0, 'n' },
  926       { "messages-origin",      1, 0, 'm' },
  927       { "pidfile",              1, 0,  0  },
  928       { "port",                 1, 0, 'p' },
  929       { "service",              1, 0, 's' },
  930       { "disable-ssl",          0, 0, 't' },
  931       { "disable-ssl-menu",     0, 0,  0  },
  932       { "disable-utmp-logging", 0, 0,  0  },
  933       { "quiet",                0, 0, 'q' },
  934       { "unixdomain-only",      1, 0,  0, },
  935       { "user",                 1, 0, 'u' },
  936       { "user-css",             1, 0,  0  },
  937       { "verbose",              0, 0, 'v' },
  938       { "version",              0, 0,  0  },
  939       { "disable-peer-check",   0, 0,  0  },
  940       { 0,                  0, 0,  0  } };
  941     int idx                = -1;
  942     int c                  = getopt_long(argc, argv, optstring, options, &idx);
  943     if (c > 0) {
  944       for (int i = 0; options[i].name; i++) {
  945         if (options[i].val == c) {
  946           idx              = i;
  947           break;
  948         }
  949       }
  950     } else if (c < 0) {
  951       break;
  952     }
  953     if (idx-- <= 0) {
  954       // Help (or invalid argument)
  955       if (idx < -1) {
  956         fatal("[server] Failed to parse command line!");
  957       } else {
  958         usage();
  959       }
  960       exit(0);
  961     } else if (!idx--) {
  962       // Background
  963       if (cgi) {
  964         fatal("[config] CGI and background operations are mutually exclusive!");
  965       }
  966       demonize            = 1;
  967       if (optarg && pidfile) {
  968         fatal("[config] Only one pidfile can be given!");
  969       }
  970       if (optarg && *optarg) {
  971         check(pidfile     = strdup(optarg));
  972       }
  973     } else if (!idx--) {
  974       // Certificate
  975       if (!hasSSL) {
  976         warn("[config] Ignoring certificate directory, as SSL support is unavailable.");
  977       }
  978       if (certificateFd >= 0) {
  979         fatal("[config] Cannot set both a certificate directory and file handle!");
  980       }
  981       if (certificateDir) {
  982         fatal("[config] Only one certificate directory can be selected!");
  983       }
  984       struct stat st;
  985       if (!optarg || !*optarg || stat(optarg, &st) || !S_ISDIR(st.st_mode)) {
  986         fatal("[config] Option --cert expects a directory name!");
  987       }
  988       check(certificateDir = strdup(optarg));
  989     } else if (!idx--) {
  990       // Certificate file descriptor
  991       if (!hasSSL) {
  992         warn("[config] Ignoring certificate directory, as SSL support is unavailable.");
  993       }
  994       if (certificateDir) {
  995         fatal("[config] Cannot set both a certificate directory and file handle!");
  996       }
  997       if (certificateFd >= 0) {
  998         fatal("[config] Only one certificate file handle can be provided!");
  999       }
 1000       if (!optarg || *optarg < '0' || *optarg > '9') {
 1001         fatal("[config] Option --cert-fd expects a valid file handle.");
 1002       }
 1003       int tmpFd            = strtoint(optarg, 3, INT_MAX);
 1004       certificateFd        = dup(tmpFd);
 1005       if (certificateFd < 0) {
 1006         fatal("[config] Invalid certificate file handle!");
 1007       }
 1008       check(!NOINTR(close(tmpFd)));
 1009     } else if (!idx--) {
 1010       // CSS
 1011       struct stat st;
 1012       if (!optarg || !*optarg || stat(optarg, &st) || !S_ISREG(st.st_mode)) {
 1013         fatal("[config] Option --css expects a file name!");
 1014       }
 1015       FILE *css            = fopen(optarg, "r");
 1016       if (!css) {
 1017         fatal("[config] Cannot read style sheet \"%s\"!", optarg);
 1018       } else {
 1019         check(cssStyleSheet= realloc(cssStyleSheet, strlen(cssStyleSheet) +
 1020                                      st.st_size + 2));
 1021         char *newData      = strrchr(cssStyleSheet, '\000');
 1022         *newData++         = '\n';
 1023         if (fread(newData, st.st_size, 1, css) != 1) {
 1024           fatal("[config] Failed to read style sheet \"%s\"!", optarg);
 1025         }
 1026         newData[st.st_size]= '\000';
 1027         fclose(css);
 1028       }
 1029     } else if (!idx--) {
 1030       // CGI
 1031       if (demonize) {
 1032         fatal("[config] CGI and background operations are mutually exclusive!");
 1033       }
 1034       if (pidfile) {
 1035         fatal("[config] CGI operation and --pidfile are mutually exclusive!");
 1036       }
 1037       if (port) {
 1038         fatal("[config] Cannot specify a port for CGI operation!");
 1039       }
 1040       cgi                  = 1;
 1041       if (optarg && *optarg) {
 1042         char *ptr          = strchr(optarg, '-');
 1043         if (!ptr) {
 1044           fatal("[config] Syntax error in port range specification!");
 1045         }
 1046         *ptr               = '\000';
 1047         portMin            = strtoint(optarg, 1, 65535);
 1048         *ptr               = '-';
 1049         portMax            = strtoint(ptr + 1, portMin, 65535);
 1050       }
 1051     } else if (!idx--) {
 1052       // Debug
 1053       if (!logIsDefault() && !logIsDebug()) {
 1054         fatal("[config] Option --debug is mutually exclusive with --quiet and --verbose!");
 1055       }
 1056       verbosity            = MSG_DEBUG;
 1057       logSetLogLevel(verbosity);
 1058     } else if (!idx--) {
 1059       // Static file
 1060       if (!optarg || !*optarg) {
 1061         fatal("[config] Option --static-file expects an argument!");
 1062       }
 1063       char *ptr, *path, *file;
 1064       if ((ptr             = strchr(optarg, ':')) == NULL) {
 1065         fatal("[config] Syntax error in static-file definition \"%s\"!",
 1066               optarg);
 1067       }
 1068       check(path           = malloc(ptr - optarg + 1));
 1069       memcpy(path, optarg, ptr - optarg);
 1070       path[ptr - optarg]   = '\000';
 1071       check(file           = strdup(ptr + 1));
 1072       if (getRefFromHashMap(externalFiles, path)) {
 1073         fatal("[config] Duplicate static-file definition for \"%s\"!", path);
 1074       }
 1075       addToHashMap(externalFiles, path, file);
 1076     } else if (!idx--) {
 1077       // Group
 1078       if (runAsGroup >= 0) {
 1079         fatal("[config] Duplicate --group option.");
 1080       }
 1081       if (!optarg || !*optarg) {
 1082         fatal("[config] Option --group expects a group name.");
 1083       }
 1084       runAsGroup           = parseGroupArg(optarg, NULL);
 1085     } else if (!idx--) {
 1086       // Linkify
 1087       if (!optarg || !*optarg) {
 1088         fatal("[config] Option --linkify expects an argument.");
 1089       }
 1090       if (!strcmp(optarg, "none")) {
 1091         linkifyURLs        = 0;
 1092       } else if (!strcmp(optarg, "normal")) {
 1093         linkifyURLs        = 1;
 1094       } else if (!strcmp(optarg, "aggressive")) {
 1095         linkifyURLs        = 2;
 1096       } else {
 1097         fatal("[config] Invalid argument for --linkify. Must be \"none\", \"normal\", "
 1098               "or \"aggressive\".");
 1099       }
 1100     } else if (!idx--) {
 1101       // Localhost Only
 1102       localhostOnly        = 1;
 1103     } else if (!idx--) {
 1104       // No Beep
 1105       noBeep               = 1;
 1106     } else if (!idx--) {
 1107       // Numeric
 1108       numericHosts         = 1;
 1109     } else if (!idx--) {
 1110       // Messages origin
 1111       if (messagesOrigin) {
 1112         fatal("[config] Duplicated --messages-origin option.");
 1113       }
 1114       if (!optarg || !*optarg) {
 1115         fatal("[config] Option --messages-origin expects an argument.");
 1116       }
 1117       check(messagesOrigin = strdup(optarg));
 1118     } else if (!idx--) {
 1119       // Pidfile
 1120       if (cgi) {
 1121         fatal("[config] CGI operation and --pidfile are mutually exclusive");
 1122       }
 1123       if (!optarg || !*optarg) {
 1124         fatal("[config] Must specify a filename for --pidfile option");
 1125       }
 1126       if (pidfile) {
 1127         fatal("[config] Only one pidfile can be given");
 1128       }
 1129       check(pidfile        = strdup(optarg));
 1130     } else if (!idx--) {
 1131       // Port
 1132       if (port) {
 1133         fatal("[config] Duplicate --port option!");
 1134       }
 1135       if (cgi) {
 1136         fatal("[config] Cannot specifiy a port for CGI operation");
 1137       }
 1138       if (!optarg || *optarg < '0' || *optarg > '9') {
 1139         fatal("[config] Option --port expects a port number.");
 1140       }
 1141       port = strtoint(optarg, 1, 65535);
 1142     } else if (!idx--) {
 1143       // Service
 1144       if (!optarg || !*optarg) {
 1145         fatal("[config] Option \"--service\" expects an argument.");
 1146       }
 1147       struct Service *service;
 1148       service              = newService(optarg);
 1149       if (getRefFromHashMap(serviceTable, service->path)) {
 1150         fatal("[config] Duplicate service description for \"%s\".", service->path);
 1151       }
 1152       addToHashMap(serviceTable, service->path, (char *)service);
 1153     } else if (!idx--) {
 1154       // Disable SSL
 1155       if (!hasSSL) {
 1156         warn("[config] Ignoring disable-ssl option, as SSL support is unavailable.");
 1157       }
 1158       enableSSL            = 0;
 1159       forceSSL             = 0;
 1160     } else if (!idx--) {
 1161       // Disable SSL Menu
 1162       if (!hasSSL) {
 1163         warn("[config] Ignoring disable-ssl-menu option, as SSL support is unavailable.");
 1164       }
 1165       enableSSLMenu        = 0;
 1166     } else if (!idx--) {
 1167       // Disable UTMP logging
 1168       enableUtmpLogging    = 0;
 1169     } else if (!idx--) {
 1170       // Quiet
 1171       if (!logIsDefault() && !logIsQuiet()) {
 1172          fatal("[config] Option --quiet is mutually exclusive with --debug and --verbose!");
 1173       }
 1174       verbosity            = MSG_QUIET;
 1175       logSetLogLevel(verbosity);
 1176     } else if (!idx--) {
 1177       // Unix domain only
 1178       if (!optarg || !*optarg) {
 1179         fatal("[config] Option --unixdomain-only expects an argument!");
 1180       }
 1181       char *ptr, *s, *tmp;
 1182 
 1183       // Unix domain path
 1184       s                    = optarg;
 1185       ptr                  = strchr(s, ':');
 1186       if (ptr == NULL || ptr == s || ptr - s >= UNIX_PATH_MAX) {
 1187         fatal("[config] Syntax error in unixdomain-only path definition \"%s\".",
 1188               optarg);
 1189       }
 1190       check(unixDomainPath = strndup(s, ptr - s));
 1191 
 1192       // Unix domain user
 1193       s                    = ptr + 1;
 1194       ptr                  = strchr(s, ':');
 1195       if (ptr == NULL || ptr == s) {
 1196         fatal("[config] Syntax error in unixdomain-only user definition \"%s\".",
 1197               optarg);
 1198       }
 1199       check(tmp            = strndup(s, ptr - s));
 1200       unixDomainUser       = parseUserArg(tmp, NULL);
 1201       free(tmp);
 1202 
 1203       // Unix domain group
 1204       s                    = ptr + 1;
 1205       ptr                  = strchr(s, ':');
 1206       if (ptr == NULL || ptr == s) {
 1207         fatal("[config] Syntax error in unixdomain-only group definition \"%s\".",
 1208               optarg);
 1209       }
 1210       check(tmp            = strndup(s, ptr - s));
 1211       unixDomainGroup      = parseGroupArg(tmp, NULL);
 1212       free(tmp);
 1213 
 1214       // Unix domain chmod
 1215       s                    = ptr + 1;
 1216       if (strlen(ptr) == 1) {
 1217         fatal("[config] Syntax error in unixdomain-only chmod definition \"%s\".",
 1218               optarg);
 1219       }
 1220       unixDomainChmod      = strtol(s, NULL, 8);
 1221 
 1222     } else if (!idx--) {
 1223       // User
 1224       if (runAsUser >= 0) {
 1225         fatal("[config] Duplicate --user option.");
 1226       }
 1227       if (!optarg || !*optarg) {
 1228         fatal("[config] Option --user expects a user name.");
 1229       }
 1230       runAsUser            = parseUserArg(optarg, NULL);
 1231     } else if (!idx--) {
 1232       // User CSS
 1233       if (!optarg || !*optarg) {
 1234         fatal("[config] Option --user-css expects a list of styles sheets "
 1235               "and labels!");
 1236       }
 1237       parseUserCSS(&userCSSList, optarg);
 1238     } else if (!idx--) {
 1239       // Verbose
 1240       if (!logIsDefault() && (!logIsInfo() || logIsDebug())) {
 1241          fatal("[config] Option --verbose is mutually exclusive with --debug and --quiet!");
 1242       }
 1243       verbosity            = MSG_INFO;
 1244       logSetLogLevel(verbosity);
 1245     } else if (!idx--) {
 1246       // Version
 1247       printf("ShellInABox version " VERSION VCS_REVISION "\n");
 1248       exit(0);
 1249     } else if (!idx--) {
 1250       // disable-peer-check
 1251       peerCheckEnabled = 0;
 1252     }
 1253   }
 1254   if (optind != argc) {
 1255     fatal("[config] Failed to parse command line!");
 1256   }
 1257   char *buf                = NULL;
 1258   check(argc >= 1);
 1259 
 1260   info("[server] Version " VERSION VCS_REVISION);
 1261   for (int i = 0; i < argc; i++) {
 1262     buf                    = stringPrintf(buf, "%s ", argv[i]);
 1263   }
 1264   info("[server] Command line: %s", buf);
 1265   free(buf);
 1266 
 1267   // If the user did not specify a port, use the default one
 1268   if (!cgi && !port) {
 1269     port                   = PORTNUM;
 1270   }
 1271 
 1272   // If the user did not register any services, provide the default service
 1273   if (!getHashmapSize(serviceTable)) {
 1274     addToHashMap(serviceTable, "/",
 1275                  (char *)newService(
 1276 #ifdef HAVE_BIN_LOGIN
 1277                                     geteuid() ? ":SSH" : ":LOGIN"
 1278 #else
 1279                                     ":SSH"
 1280 #endif
 1281                                     ));
 1282   }
 1283   enumerateServices(serviceTable);
 1284   deleteHashMap(serviceTable);
 1285 
 1286   // Do not allow non-root URLs for CGI operation
 1287   if (cgi) {
 1288     for (int i = 0; i < numServices; i++) {
 1289       if (strcmp(services[i]->path, "/")) {
 1290         fatal("[config] Non-root service URLs are incompatible with CGI operation");
 1291       }
 1292     }
 1293     check(cgiSessionKey    = newSessionKey());
 1294   }
 1295 
 1296   if (demonize) {
 1297     pid_t pid;
 1298     check((pid             = fork()) >= 0);
 1299     if (pid) {
 1300       _exit(0);
 1301     }
 1302     setsid();
 1303   }
 1304   if (pidfile) {
 1305 #ifndef O_LARGEFILE
 1306 #define O_LARGEFILE 0
 1307 #endif
 1308     int fd                 = NOINTR(open(pidfile,
 1309                                          O_WRONLY|O_TRUNC|O_LARGEFILE|O_CREAT,
 1310                                          0644));
 1311     if (fd >= 0) {
 1312       char buf[40];
 1313       NOINTR(write(fd, buf, snprintf(buf, 40, "%d", (int)getpid())));
 1314       check(!NOINTR(close(fd)));
 1315     } else {
 1316       free((char *)pidfile);
 1317       pidfile              = NULL;
 1318     }
 1319   }
 1320 }
 1321 
 1322 static void removeLimits() {
 1323   static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
 1324   for (unsigned i = 0; i < sizeof(res)/sizeof(int); i++) {
 1325     struct rlimit rl;
 1326     getrlimit(res[i], &rl);
 1327     if (rl.rlim_max < RLIM_INFINITY) {
 1328       rl.rlim_max  = RLIM_INFINITY;
 1329       setrlimit(res[i], &rl);
 1330       getrlimit(res[i], &rl);
 1331     }
 1332     if (rl.rlim_cur < rl.rlim_max) {
 1333       rl.rlim_cur  = rl.rlim_max;
 1334       setrlimit(res[i], &rl);
 1335     }
 1336   }
 1337 }
 1338 
 1339 static void setUpSSL(Server *server) {
 1340 
 1341   serverSetupSSL(server, enableSSL, forceSSL);
 1342 
 1343   // Enable SSL support (if available)
 1344   if (enableSSL) {
 1345     check(serverSupportsSSL());
 1346     if (certificateFd >= 0) {
 1347       serverSetCertificateFd(server, certificateFd);
 1348     } else if (certificateDir) {
 1349       char *tmp;
 1350       if (strchr(certificateDir, '%')) {
 1351         fatal("[ssl] Invalid certificate directory name \"%s\".", certificateDir);
 1352       }
 1353       check(tmp = stringPrintf(NULL, "%s/certificate%%s.pem", certificateDir));
 1354       serverSetCertificate(server, tmp, 1);
 1355       free(tmp);
 1356     } else {
 1357       serverSetCertificate(server, "certificate%s.pem", 1);
 1358     }
 1359   }
 1360 }
 1361 
 1362 int main(int argc, char * const argv[]) {
 1363 #ifdef HAVE_SYS_PRCTL_H
 1364   // Disable core files
 1365   prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
 1366 #endif
 1367   struct rlimit rl = { 0 };
 1368   setrlimit(RLIMIT_CORE, &rl);
 1369   removeLimits();
 1370 
 1371   // Parse command line arguments
 1372   parseArgs(argc, argv);
 1373 
 1374   // Fork the launcher process, allowing us to drop privileges in the main
 1375   // process.
 1376   int launcherFd  = forkLauncher();
 1377 
 1378   // Make sure that our timestamps will print in the standard format
 1379   setlocale(LC_TIME, "POSIX");
 1380 
 1381   // Create a new web server
 1382   Server *server;
 1383   if (port) {
 1384     check(server  = newServer(localhostOnly, port));
 1385     dropPrivileges();
 1386     setUpSSL(server);
 1387   } else {
 1388     // For CGI operation we fork the new server, so that it runs in the
 1389     // background.
 1390     pid_t pid;
 1391     int   fds[2];
 1392     dropPrivileges();
 1393     check(!pipe(fds));
 1394     check((pid    = fork()) >= 0);
 1395     if (pid) {
 1396       // Wait for child to output initial HTML page
 1397       char wait;
 1398       check(!NOINTR(close(fds[1])));
 1399       check(!NOINTR(read(fds[0], &wait, 1)));
 1400       check(!NOINTR(close(fds[0])));
 1401       _exit(0);
 1402     }
 1403     check(!NOINTR(close(fds[0])));
 1404     check(server  = newCGIServer(localhostOnly, portMin, portMax,
 1405                                  AJAX_TIMEOUT));
 1406     cgiServer     = server;
 1407     setUpSSL(server);
 1408 
 1409     // Output a <frameset> that includes our root page
 1410     check(port    = serverGetListeningPort(server));
 1411     printf("X-ShellInABox-Port: %d\r\n"
 1412            "X-ShellInABox-Pid: %d\r\n"
 1413            "X-ShellInABox-Session: %s\r\n"
 1414            "Content-type: text/html; charset=utf-8\r\n\r\n",
 1415            port, getpid(), cgiSessionKey);
 1416     UNUSED(cgiRootSize);
 1417     printfUnchecked(cgiRootStart, port, cgiSessionKey);
 1418     fflush(stdout);
 1419     check(!NOINTR(close(fds[1])));
 1420     closeAllFds((int []){ launcherFd, serverGetFd(server) }, 2);
 1421     logSetLogLevel(MSG_QUIET);
 1422   }
 1423 
 1424   // Set log file format
 1425   serverSetNumericHosts(server, numericHosts ||
 1426                         logIsQuiet() || logIsDefault());
 1427 
 1428   // Disable /quit handler
 1429   serverRegisterHttpHandler(server, "/quit", NULL, NULL);
 1430 
 1431   // Register HTTP handler(s)
 1432   for (int i = 0; i < numServices; i++) {
 1433     serverRegisterHttpHandler(server, services[i]->path,
 1434                               shellInABoxHttpHandler, services[i]);
 1435   }
 1436 
 1437   // Register handlers for external files
 1438   iterateOverHashMap(externalFiles, registerExternalFiles, server);
 1439 
 1440   // Start the server
 1441   if (!sigsetjmp(jmpenv, 1)) {
 1442     // Clean up upon orderly shut down. Do _not_ cleanup if we die
 1443     // unexpectedly, as we cannot guarantee if we are still in a valid
 1444     // static. This means, we should never catch SIGABRT.
 1445     static const int signals[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM };
 1446     struct sigaction sa;
 1447     memset(&sa, 0, sizeof(sa));
 1448     sa.sa_sigaction = sigHandler;
 1449     sa.sa_flags   = SA_SIGINFO | SA_RESETHAND;
 1450     for (int i = 0; i < sizeof(signals)/sizeof(*signals); ++i) {
 1451       sigaction(signals[i], &sa, NULL);
 1452     }
 1453     serverLoop(server);
 1454   }
 1455 
 1456   // Clean up
 1457   deleteServer(server);
 1458   finishAllSessions();
 1459   deleteHashMap(externalFiles);
 1460   for (int i = 0; i < numServices; i++) {
 1461     deleteService(services[i]);
 1462   }
 1463   free(services);
 1464   free(certificateDir);
 1465   free(cgiSessionKey);
 1466   free(messagesOrigin);
 1467   if (pidfile) {
 1468     // As a convenience, remove the pidfile, if it is still the version that
 1469     // we wrote. In general, pidfiles are not expected to be incredibly
 1470     // reliable, as there is no way to properly deal with multiple programs
 1471     // accessing the same pidfile. But we at least make a best effort to be
 1472     // good citizens.
 1473     char buf[40];
 1474     int fd        = open(pidfile, O_RDONLY);
 1475     if (fd >= 0) {
 1476       ssize_t sz;
 1477       NOINTR(sz   = read(fd, buf, sizeof(buf)-1));
 1478       NOINTR(close(fd));
 1479       if (sz > 0) {
 1480         buf[sz]   = '\000';
 1481         if (atoi(buf) == getpid()) {
 1482           unlink(pidfile);
 1483         }
 1484       }
 1485     }
 1486     free((char *)pidfile);
 1487   }
 1488   info("[server] Done");
 1489   _exit(0);
 1490 }