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