]> andersk Git - test.git/blob - shellinabox/shellinaboxd.c
Use JavaScript redirection for attaching the missing slash to
[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-2009 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 <stdarg.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <sys/resource.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <unistd.h>
63
64 #ifdef HAVE_SYS_PRCTL_H
65 #include <sys/prctl.h>
66 #endif
67
68 #include "libhttp/http.h"
69 #include "logging/logging.h"
70 #include "shellinabox/externalfile.h"
71 #include "shellinabox/launcher.h"
72 #include "shellinabox/privileges.h"
73 #include "shellinabox/service.h"
74 #include "shellinabox/session.h"
75
76 #define PORTNUM           4200
77 #define MAX_RESPONSE      2048
78
79 static int     port;
80 static int     portMin;
81 static int     portMax;
82 static int     localhostOnly = 0;
83 static int     noBeep        = 0;
84 static int     numericHosts  = 0;
85 static int     enableSSL     = 1;
86 static int     enableSSLMenu = 1;
87 static int     linkifyURLs   = 1;
88 static char    *certificateDir;
89 static int     certificateFd = -1;
90 static HashMap *externalFiles;
91 static Server  *cgiServer;
92 static char    *cgiSessionKey;
93 static int     cgiSessions;
94
95 static char *jsonEscape(const char *buf, int len) {
96   static const char *hexDigit = "0123456789ABCDEF";
97
98   // Determine the space that is needed to encode the buffer
99   int count                   = 0;
100   const char *ptr             = buf;
101   for (int i = 0; i < len; i++) {
102     unsigned char ch          = *(unsigned char *)ptr++;
103     if (ch < ' ') {
104       switch (ch) {
105       case '\b': case '\f': case '\n': case '\r': case '\t':
106         count                += 2;
107         break;
108       default:
109         count                += 6;
110         break;
111       }
112     } else if (ch == '"' || ch == '\\' || ch == '/') {
113       count                  += 2;
114     } else if (ch > '\x7F') {
115       count                  += 6;
116     } else {
117       count++;
118     }
119   }
120
121   // Encode the buffer using JSON string escaping
122   char *result;
123   check(result                = malloc(count + 1));
124   char *dst                   = result;
125   ptr                         = buf;
126   for (int i = 0; i < len; i++) {
127     unsigned char ch          = *(unsigned char *)ptr++;
128     if (ch < ' ') {
129       *dst++                  = '\\';
130       switch (ch) {
131       case '\b': *dst++       = 'b'; break;
132       case '\f': *dst++       = 'f'; break;
133       case '\n': *dst++       = 'n'; break;
134       case '\r': *dst++       = 'r'; break;
135       case '\t': *dst++       = 't'; break;
136       default:
137       unicode:
138         *dst++                = 'u';
139         *dst++                = '0';
140         *dst++                = '0';
141         *dst++                = hexDigit[ch >> 4];
142         *dst++                = hexDigit[ch & 0xF];
143         break;
144       }
145     } else if (ch == '"' || ch == '\\' || ch == '/') {
146       *dst++                  = '\\';
147       *dst++                  = ch;
148     } else if (ch > '\x7F') {
149       *dst++                  = '\\';
150       goto unicode;
151     } else {
152       *dst++                  = ch;
153     }
154   }
155   *dst++                      = '\000';
156   return result;
157 }
158
159 static int printfUnchecked(const char *format, ...) {
160   // Some Linux distributions enable -Wformat=2 by default. This is a
161   // very unfortunate decision, as that option generates a lot of false
162   // positives. We try to work around the problem by defining an unchecked
163   // version of "printf()"
164   va_list ap;
165   va_start(ap, format);
166   int rc = vprintf(format, ap);
167   va_end(ap);
168   return rc;
169 }
170
171 static int completePendingRequest(struct Session *session,
172                                   const char *buf, int len, int maxLength) {
173   // If there is no pending HTTP request, save the data and return
174   // immediately.
175   if (!session->http) {
176     if (len) {
177       if (session->buffered) {
178         check(session->buffered = realloc(session->buffered,
179                                           session->len + len));
180         memcpy(session->buffered + session->len, buf, len);
181         session->len           += len;
182       } else {
183         check(session->buffered = malloc(len));
184         memcpy(session->buffered, buf, len);
185         session->len            = len;
186       }
187     }
188   } else {
189     // If we have a pending HTTP request, we can reply to it, now.
190     char *data;
191     if (session->buffered) {
192       check(session->buffered   = realloc(session->buffered,
193                                           session->len + len));
194       memcpy(session->buffered + session->len, buf, len);
195       session->len             += len;
196       if (maxLength > 0 && session->len > maxLength) {
197         data                    = jsonEscape(session->buffered, maxLength);
198         session->len           -= maxLength;
199         memmove(session->buffered, session->buffered + maxLength,
200                 session->len);
201       } else {
202         data                    = jsonEscape(session->buffered, session->len);
203         free(session->buffered);
204         session->buffered       = NULL;
205         session->len            = 0;
206       }
207     } else {
208       if (maxLength > 0 && len > maxLength) {
209         session->len            = len - maxLength;
210         check(session->buffered = malloc(session->len));
211         memcpy(session->buffered, buf + maxLength, session->len);
212         data                    = jsonEscape(buf, maxLength);
213       } else {
214         data                    = jsonEscape(buf, len);
215       }
216     }
217     
218     char *json                  = stringPrintf(NULL, "{"
219                                                "\"session\":\"%s\","
220                                                "\"data\":\"%s\""
221                                                "}",
222                                                session->sessionKey, data);
223     free(data);
224     HttpConnection *http        = session->http;
225     char *response              = stringPrintf(NULL,
226                                              "HTTP/1.1 200 OK\r\n"
227                                              "Content-Type: application/json; "
228                                              "charset=utf-8\r\n"
229                                              "Content-Length: %ld\r\n"
230                                              "Cache-Control: no-cache\r\n"
231                                              "\r\n"
232                                              "%s",
233                                              (long)strlen(json),
234                                              strcmp(httpGetMethod(http),
235                                                     "HEAD") ? json : "");
236     free(json);
237     session->http               = NULL;
238     httpTransfer(http, response, strlen(response));
239   }
240   if (session->done && !session->buffered) {
241     finishSession(session);
242     return 0;
243   }
244   return 1;
245 }
246
247 static void sessionDone(void *arg) {
248   debug("Child terminated");
249   struct Session *session = (struct Session *)arg;
250   session->done           = 1;
251   addToGraveyard(session);
252   completePendingRequest(session, "", 0, INT_MAX);
253 }
254
255 static int handleSession(struct ServerConnection *connection, void *arg,
256                          short *events, short revents) {
257   struct Session *session       = (struct Session *)arg;
258   session->connection           = connection;
259   int len                       = MAX_RESPONSE - session->len;
260   if (len <= 0) {
261     len                         = 1;
262   }
263   char buf[len];
264   int bytes                     = 0;
265   if (revents & POLLIN) {
266     bytes                       = NOINTR(read(session->pty, buf, len));
267     if (bytes <= 0) {
268       return 0;
269     }
270   }
271   int timedOut                  = serverGetTimeout(connection) < 0;
272   if (bytes || timedOut) {
273     if (!session->http && timedOut) {
274       debug("Timeout. Closing session.");
275       return 0;
276     }
277     check(!session->done);
278     check(completePendingRequest(session, buf, bytes, MAX_RESPONSE));
279     connection                  = serverGetConnection(session->server,
280                                                       connection,
281                                                       session->pty);
282     session->connection         = connection;
283     if (session->len >= MAX_RESPONSE) {
284       serverConnectionSetEvents(session->server, connection, 0);
285     }
286     serverSetTimeout(connection, AJAX_TIMEOUT);
287     return 1;
288   } else {
289     return 0;
290   }
291 }
292
293 static int invalidatePendingHttpSession(void *arg, const char *key,
294                                         char **value) {
295   struct Session *session = *(struct Session **)value;
296   if (session->http && session->http == (HttpConnection *)arg) {
297     debug("Clearing pending HTTP connection for session %s", key);
298     session->http         = NULL;
299     serverDeleteConnection(session->server, session->pty);
300
301     // Return zero in order to remove this HTTP from the "session" hashmap
302     return 0;
303   }
304
305   // If the session is still in use, do not remove it from the "sessions" map
306   return 1;
307 }
308
309 static int dataHandler(HttpConnection *http, struct Service *service,
310                        const char *buf, int len, URL *url) {
311   if (!buf) {
312     // Somebody unexpectedly closed our http connection (e.g. because of a
313     // timeout). This is the last notification that we will get.
314     deleteURL(url);
315     iterateOverSessions(invalidatePendingHttpSession, http);
316     return HTTP_DONE;
317   }
318
319   // Find an existing session, or create the record for a new one
320   int isNew;
321   struct Session *session = findCGISession(&isNew, http, url, cgiSessionKey);
322   if (session == NULL) {
323     httpSendReply(http, 400, "Bad Request", NO_MSG);
324     return HTTP_DONE;
325   }
326
327   // Sanity check
328   if (!isNew && strcmp(session->peerName, httpGetPeerName(http))) {
329     error("Peername changed from %s to %s",
330           session->peerName, httpGetPeerName(http));
331     httpSendReply(http, 400, "Bad Request", NO_MSG);
332     return HTTP_DONE;
333   }
334
335   const HashMap *args     = urlGetArgs(session->url);
336   int oldWidth            = session->width;
337   int oldHeight           = session->height;
338   const char *width       = getFromHashMap(args, "width");
339   const char *height      = getFromHashMap(args, "height");
340   const char *keys        = getFromHashMap(args, "keys");
341
342   // Adjust window dimensions if provided by client
343   if (width && height) {
344     session->width        = atoi(width);
345     session->height       = atoi(height);
346   }
347
348   // Create a new session, if the client did not provide an existing one
349   if (isNew) {
350     if (cgiServer && cgiSessions++) {
351       serverExitLoop(cgiServer, 1);
352       abandonSession(session);
353       httpSendReply(http, 400, "Bad Request", NO_MSG);
354       return HTTP_DONE;
355     }
356     session->http         = http;
357     if (launchChild(service->id, session) < 0) {
358       abandonSession(session);
359       httpSendReply(http, 500, "Internal Error", NO_MSG);
360       return HTTP_DONE;
361     }
362     if (cgiServer) {
363       terminateLauncher();
364     }
365     session->connection   = serverAddConnection(httpGetServer(http),
366                                                 session->pty, handleSession,
367                                                 sessionDone, session);
368     serverSetTimeout(session->connection, AJAX_TIMEOUT);
369   }
370
371   // Reset window dimensions of the pseudo TTY, if changed since last time set.
372   if (session->width > 0 && session->height > 0 &&
373       (session->width != oldWidth || session->height != oldHeight)) {
374     debug("Window size changed to %dx%d", session->width, session->height);
375     setWindowSize(session->pty, session->width, session->height);
376   }
377
378   // Process keypresses, if any. Then send a synchronous reply.
379   if (keys) {
380     char *keyCodes;
381     check(keyCodes        = malloc(strlen(keys)/2));
382     int len               = 0;
383     for (const unsigned char *ptr = (const unsigned char *)keys; ;) {
384       unsigned c0         = *ptr++;
385       if (c0 < '0' || (c0 > '9' && c0 < 'A') ||
386           (c0 > 'F' && c0 < 'a') || c0 > 'f') {
387         break;
388       }
389       unsigned c1         = *ptr++;
390       if (c1 < '0' || (c1 > '9' && c1 < 'A') ||
391           (c1 > 'F' && c1 < 'a') || c1 > 'f') {
392         break;
393       }
394       keyCodes[len++]     = 16*((c0 & 0xF) + 9*(c0 > '9')) +
395                                 (c1 & 0xF) + 9*(c1 > '9');
396     }
397     if (write(session->pty, keyCodes, len) < 0 && errno == EAGAIN) {
398       completePendingRequest(session, "\007", 1, MAX_RESPONSE);
399     }
400     free(keyCodes);
401     httpSendReply(http, 200, "OK", " ");
402     check(session->http != http);
403     return HTTP_DONE;
404   } else {
405     // This request is polling for data. Finish any pending requests and
406     // queue (or process) a new one.
407     if (session->http && session->http != http &&
408         !completePendingRequest(session, "", 0, MAX_RESPONSE)) {
409       httpSendReply(http, 400, "Bad Request", NO_MSG);
410       return HTTP_DONE;
411     }
412     session->http         = http;
413   }
414
415   session->connection     = serverGetConnection(session->server,
416                                                 session->connection,
417                                                 session->pty);
418   if (session->buffered || isNew) {
419     if (completePendingRequest(session, "", 0, MAX_RESPONSE) &&
420         session->connection) {
421       // Reset the timeout, as we just received a new request.
422       serverSetTimeout(session->connection, AJAX_TIMEOUT);
423       if (session->len < MAX_RESPONSE) {
424         // Re-enable input on the child's pty
425         serverConnectionSetEvents(session->server, session->connection,POLLIN);
426       }
427     }
428     return HTTP_DONE;
429   } else if (session->connection) {
430     // Re-enable input on the child's pty
431     serverConnectionSetEvents(session->server, session->connection, POLLIN);
432     serverSetTimeout(session->connection, AJAX_TIMEOUT);
433   }
434
435   return HTTP_SUSPEND;
436 }
437
438 static void serveStaticFile(HttpConnection *http, const char *contentType,
439                             const char *start, const char *end) {
440   char *response   = stringPrintf(NULL,
441                                   "HTTP/1.1 200 OK\r\n"
442                                   "Content-Type: %s\r\n"
443                                   "Content-Length: %ld\r\n"
444                                   "\r\n",
445                                   contentType, (long)(end - start));
446   int len          = strlen(response);
447   if (strcmp(httpGetMethod(http), "HEAD")) {
448     check(response = realloc(response, len + (end - start)));
449     memcpy(response + len, start, end - start);
450     len           += end - start;
451   }
452   httpTransfer(http, response, len);
453 }
454
455 static const char *addr(const char *a) {
456   // Work-around for a gcc bug that could occasionally generate invalid
457   // assembly instructions when optimizing code too agressively.
458   asm volatile("");
459   return a;
460 }
461
462 static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
463                                   const char *buf, int len) {
464   checkGraveyard();
465   URL *url                = newURL(http, buf, len);
466   const HashMap *headers  = httpGetHeaders(http);
467   const char *contentType = getFromHashMap(headers, "content-type");
468
469   // Normalize the path info
470   const char *pathInfo    = urlGetPathInfo(url);
471   while (*pathInfo == '/') {
472     pathInfo++;
473   }
474   const char *endPathInfo;
475   for (endPathInfo        = pathInfo;
476        *endPathInfo && *endPathInfo != '/';
477        endPathInfo++) {
478   }
479   int pathInfoLength      = endPathInfo - pathInfo;
480
481   if (!pathInfoLength ||
482       (pathInfoLength == 5 && !memcmp(pathInfo, "plain", 5)) ||
483       (pathInfoLength == 6 && !memcmp(pathInfo, "secure", 6))) {
484     // The root page serves the AJAX application.
485     if (contentType &&
486         !strncasecmp(contentType, "application/x-www-form-urlencoded", 33)) {
487       // XMLHttpRequest carrying data between the AJAX application and the
488       // client session.
489       return dataHandler(http, arg, buf, len, url);
490     }
491     extern char rootPageStart[];
492     extern char rootPageEnd[];
493     char *rootPage;
494     check(rootPage = malloc(rootPageEnd - rootPageStart + 1));
495     memcpy(rootPage, rootPageStart, rootPageEnd - rootPageStart);
496     rootPage[rootPageEnd - rootPageStart] = '\000';
497     char *html            = stringPrintf(NULL, rootPage,
498                                          enableSSL ? "true" : "false");
499     httpSendReply(http, 200, "OK", html);
500     free(rootPage);
501   } else if (pathInfoLength == 8 && !memcmp(pathInfo, "beep.wav", 8)) {
502     // Serve the audio sample for the console bell.
503     extern char beepStart[];
504     extern char beepEnd[];
505     serveStaticFile(http, "audio/x-wav", beepStart, beepEnd);
506   } else if (pathInfoLength == 11 && !memcmp(pathInfo, "favicon.ico", 11)) {
507     // Serve the favicon
508     extern char faviconStart[];
509     extern char faviconEnd[];
510     serveStaticFile(http, "image/x-icon", faviconStart, faviconEnd);
511   } else if (pathInfoLength == 14 && !memcmp(pathInfo, "ShellInABox.js", 14)) {
512     // Serve both vt100.js and shell_in_a_box.js in the same transaction.
513     // Also, indicate to the client whether the server is SSL enabled.
514     extern char vt100Start[];
515     extern char vt100End[];
516     extern char shellInABoxStart[];
517     extern char shellInABoxEnd[];
518     char *stateVars       = stringPrintf(NULL,
519                                          "serverSupportsSSL = %s;\n"
520                                          "disableSSLMenu    = %s;\n"
521                                          "suppressAllAudio  = %s;\n"
522                                          "linkifyURLs       = %d;\n\n",
523                                          enableSSL      ? "true" : "false",
524                                          !enableSSLMenu ? "true" : "false",
525                                          noBeep         ? "true" : "false",
526                                          linkifyURLs);
527     int stateVarsLength   = strlen(stateVars);
528     int contentLength     = stateVarsLength +
529                             (addr(vt100End) - addr(vt100Start)) +
530                             (addr(shellInABoxEnd) - addr(shellInABoxStart));
531     char *response        = stringPrintf(NULL,
532                              "HTTP/1.1 200 OK\r\n"
533                              "Content-Type: text/javascript; charset=utf-8\r\n"
534                              "Content-Length: %d\r\n"
535                              "\r\n",
536                              contentLength);
537     int headerLength      = strlen(response);
538     if (strcmp(httpGetMethod(http), "HEAD")) {
539       check(response      = realloc(response, headerLength + contentLength));
540       memcpy(memcpy(memcpy(
541           response + headerLength, stateVars, stateVarsLength)+stateVarsLength,
542         vt100Start, vt100End - vt100Start) + (vt100End - vt100Start),
543       shellInABoxStart, shellInABoxEnd - shellInABoxStart);
544     } else {
545       contentLength       = 0;
546     }
547     free(stateVars);
548     httpTransfer(http, response, headerLength + contentLength);
549   } else if (pathInfoLength == 10 && !memcmp(pathInfo, "styles.css", 10)) {
550     // Serve the style sheet.
551     extern char stylesStart[];
552     extern char stylesEnd[];
553     serveStaticFile(http, "text/css; charset=utf-8", stylesStart, stylesEnd);
554   } else {
555     httpSendReply(http, 404, "File not found", NO_MSG);
556   }
557
558   deleteURL(url);
559   return HTTP_DONE;
560 }
561
562 static int strtoint(const char *s, int minVal, int maxVal) {
563   char *ptr;
564   if (!*s) {
565     fatal("Missing numeric value.");
566   }
567   long l = strtol(s, &ptr, 10);
568   if (*ptr || l < minVal || l > maxVal) {
569     fatal("Range error on numeric value \"%s\".", s);
570   }
571   return l;
572 }
573
574 static void usage(void) {
575   // Drop privileges so that we can tell which uid/gid we would normally
576   // run at.
577   dropPrivileges();
578   uid_t r_uid, e_uid, s_uid;
579   uid_t r_gid, e_gid, s_gid;
580   check(!getresuid(&r_uid, &e_uid, &s_uid));
581   check(!getresgid(&r_gid, &e_gid, &s_gid));
582   const char *user  = getUserName(r_uid);
583   const char *group = getGroupName(r_gid);
584
585   message("Usage: shellinaboxd [OPTIONS]...\n"
586           "Starts an HTTP server that serves terminal emulators to AJAX "
587           "enabled browsers.\n"
588           "\n"
589           "List of command line options:\n"
590           "  -b, --background[=PIDFILE]  run in background\n"
591           "%s"
592           "      --cgi[=PORTMIN-PORTMAX] run as CGI\n"
593           "  -d, --debug                 enable debug mode\n"
594           "  -f, --static-file=URL:FILE  serve static file from URL path\n"
595           "  -g, --group=GID             switch to this group (default: %s)\n"
596           "  -h, --help                  print this message\n"
597           "      --linkify=[none|normal|agressive] default is \"normal\"\n"
598           "      --localhost-only        only listen on 127.0.0.1\n"
599           "      --no-beep               suppress all audio output\n"
600           "  -n, --numeric               do not resolve hostnames\n"
601           "  -p, --port=PORT             select a port (default: %d)\n"
602           "  -s, --service=SERVICE       define one or more services\n"
603           "%s"
604           "  -q, --quiet                 turn off all messages\n"
605           "  -u, --user=UID              switch to this user (default: %s)\n"
606           "  -v, --verbose               enable logging messages\n"
607           "      --version               prints version information\n"
608           "\n"
609           "Debug, quiet, and verbose are mutually exclusive.\n"
610           "\n"
611           "One or more --service arguments define services that should "
612           "be made available \n"
613           "through the web interface:\n"
614           "  SERVICE := <url-path> ':' APP\n"
615           "  APP     := 'LOGIN' | USER ':' CWD ':' <cmdline>\n"
616           "  USER    := %s<username> ':' <groupname>\n"
617           "  CWD     := 'HOME' | <dir>\n"
618           "\n"
619           "<cmdline> supports variable expansion:\n"
620           "  ${columns} - number of columns\n"
621           "  ${gid}     - gid id\n"
622           "  ${group}   - group name\n"
623           "  ${home}    - home directory\n"
624           "  ${lines}   - number of rows\n"
625           "  ${peer}    - name of remote peer\n"
626           "  ${uid}     - user id\n"
627           "  ${user}    - user name",
628           !serverSupportsSSL() ? "" :
629           "  -c, --cert=CERTDIR          set certificate dir "
630           "(default: $PWD)\n"
631           "      --cert-fd=FD            set certificate file from fd\n",
632           group, PORTNUM,
633           !serverSupportsSSL() ? "" :
634           "  -t, --disable-ssl           disable transparent SSL support\n"
635           "      --disable-ssl-menu      disallow changing transport mode\n",
636           user, supportsPAM() ? "'AUTH' | " : "");
637   free((char *)user);
638   free((char *)group);
639 }
640
641 static void destroyExternalFileHashEntry(void *arg, char *key, char *value) {
642   free(key);
643   free(value);
644 }
645
646 static void parseArgs(int argc, char * const argv[]) {
647   int hasSSL               = serverSupportsSSL();
648   if (!hasSSL) {
649     enableSSL              = 0;
650   }
651   int demonize             = 0;
652   int cgi                  = 0;
653   const char *pidfile      = NULL;
654   int verbosity            = MSG_DEFAULT;
655   externalFiles            = newHashMap(destroyExternalFileHashEntry, NULL);
656   HashMap *serviceTable    = newHashMap(destroyServiceHashEntry, NULL);
657   for (;;) {
658     static const char optstring[] = "+hb::c:df:g:np:s:tqu:v";
659     static struct option options[] = {
660       { "help",             0, 0, 'h' },
661       { "background",       2, 0, 'b' },
662       { "cert",             1, 0, 'c' },
663       { "cert-fd",          1, 0,  0  },
664       { "cgi",              2, 0,  0  },
665       { "debug",            0, 0, 'd' },
666       { "static-file",      1, 0, 'f' },
667       { "group",            1, 0, 'g' },
668       { "linkify",          1, 0,  0  },
669       { "localhost-only",   0, 0,  0  },
670       { "no-beep",          0, 0,  0  },
671       { "numeric",          0, 0, 'n' },
672       { "port",             1, 0, 'p' },
673       { "service",          1, 0, 's' },
674       { "disable-ssl",      0, 0, 't' },
675       { "disable-ssl-menu", 0, 0,  0  },
676       { "quiet",            0, 0, 'q' },
677       { "user",             1, 0, 'u' },
678       { "verbose",          0, 0, 'v' },
679       { "version",          0, 0,  0  },
680       { 0,                  0, 0,  0  } };
681     int idx                = -1;
682     int c                  = getopt_long(argc, argv, optstring, options, &idx);
683     if (c > 0) {
684       for (int i = 0; options[i].name; i++) {
685         if (options[i].val == c) {
686           idx              = i;
687           break;
688         }
689       }
690     } else if (c < 0) {
691       break;
692     }
693     if (idx-- <= 0) {
694       // Help (or invalid argument)
695       usage();
696       if (idx == -1) {
697         fatal("Failed to parse command line");
698       }
699       exit(0);
700     } else if (!idx--) {
701       // Background
702       if (cgi) {
703         fatal("CGI and background operations are mutually exclusive");
704       }
705       demonize            = 1;
706       if (optarg && pidfile) {
707         fatal("Only one pidfile can be given");
708       }
709       if (optarg) {
710         pidfile            = strdup(optarg);
711       }
712     } else if (!idx--) {
713       // Certificate
714       if (!hasSSL) {
715         warn("Ignoring certificate directory, as SSL support is unavailable");
716       }
717       if (certificateFd >= 0) {
718         fatal("Cannot set both a certificate directory and file handle");
719       }
720       if (certificateDir) {
721         fatal("Only one certificate directory can be selected");
722       }
723       check(certificateDir = strdup(optarg));
724     } else if (!idx--) {
725       // Certificate file descriptor
726       if (!hasSSL) {
727         warn("Ignoring certificate directory, as SSL support is unavailable");
728       }
729       if (certificateDir) {
730         fatal("Cannot set both a certificate directory and file handle");
731       }
732       if (certificateFd >= 0) {
733         fatal("Only one certificate file handle can be provided");
734       }
735       int tmpFd            = strtoint(optarg, 3, INT_MAX);
736       certificateFd        = dup(tmpFd);
737       if (certificateFd < 0) {
738         fatal("Invalid certificate file handle");
739       }
740       check(!NOINTR(close(tmpFd)));
741     } else if (!idx--) {
742       // CGI
743       if (demonize) {
744         fatal("CGI and background operations are mutually exclusive");
745       }
746       if (port) {
747         fatal("Cannot specify a port for CGI operation");
748       }
749       cgi                  = 1;
750       if (optarg) {
751         char *ptr          = strchr(optarg, '-');
752         if (!ptr) {
753           fatal("Syntax error in port range specification");
754         }
755         *ptr               = '\000';
756         portMin            = strtoint(optarg, 1, 65535);
757         *ptr               = '-';
758         portMax            = strtoint(ptr + 1, portMin, 65535);
759       }
760     } else if (!idx--) {
761       // Debug
762       if (!logIsDefault() && !logIsDebug()) {
763         fatal("--debug is mutually exclusive with --quiet and --verbose.");
764       }
765       verbosity            = MSG_DEBUG;
766       logSetLogLevel(verbosity);
767     } else if (!idx--) {
768       // Static file
769       char *ptr, *path, *file;
770       if ((ptr             = strchr(optarg, ':')) == NULL) {
771         fatal("Syntax error in static-file definition \"%s\".", optarg);
772       }
773       check(path           = malloc(ptr - optarg + 1));
774       memcpy(path, optarg, ptr - optarg);
775       path[ptr - optarg]   = '\000';
776       file                 = strdup(ptr + 1);
777       if (getRefFromHashMap(externalFiles, path)) {
778         fatal("Duplicate static-file definition for \"%s\".", path);
779       }
780       addToHashMap(externalFiles, path, file);
781     } else if (!idx--) {
782       // Group
783       if (runAsGroup >= 0) {
784         fatal("Duplicate --group option.");
785       }
786       runAsGroup           = parseGroup(optarg, NULL);
787     } else if (!idx--) {
788       // Linkify
789       if (!strcmp(optarg, "none")) {
790         linkifyURLs        = 0;
791       } else if (!strcmp(optarg, "normal")) {
792         linkifyURLs        = 1;
793       } else if (!strcmp(optarg, "aggressive")) {
794         linkifyURLs        = 2;
795       } else {
796         fatal("Invalid argument for --linkify. Must be "
797               "\"none\", \"normal\", or \"aggressive\".");
798       }
799     } else if (!idx--) {
800       // Localhost Only
801       localhostOnly        = 1;
802     } else if (!idx--) {
803       // No Beep
804       noBeep               = 1;
805     } else if (!idx--) {
806       // Numeric
807       numericHosts         = 1;
808     } else if (!idx--) {
809       // Port
810       if (port) {
811         fatal("Duplicate --port option");
812       }
813       if (cgi) {
814         fatal("Cannot specifiy a port for CGI operation");
815       }
816       port = strtoint(optarg, 1, 65535);
817     } else if (!idx--) {
818       // Service
819       struct Service *service;
820       service              = newService(optarg);
821       if (getRefFromHashMap(serviceTable, service->path)) {
822         fatal("Duplicate service description for \"%s\".", service->path);
823       }
824       addToHashMap(serviceTable, service->path, (char *)service);
825     } else if (!idx--) {
826       // Disable SSL
827       if (!hasSSL) {
828         warn("Ignoring disable-ssl option, as SSL support is unavailable");
829       }
830       enableSSL            = 0;
831     } else if (!idx--) {
832       // Disable SSL Menu
833       if (!hasSSL) {
834         warn("Ignoring disable-ssl-menu option, as SSL support is "
835              "unavailable");
836       }
837       enableSSLMenu        = 0;
838     } else if (!idx--) {
839       // Quiet
840       if (!logIsDefault() && !logIsQuiet()) {
841         fatal("--quiet is mutually exclusive with --debug and --verbose.");
842       }
843       verbosity            = MSG_QUIET;
844       logSetLogLevel(verbosity);
845     } else if (!idx--) {
846       // User
847       if (runAsUser >= 0) {
848         fatal("Duplicate --user option.");
849       }
850       runAsUser            = parseUser(optarg, NULL);
851     } else if (!idx--) {
852       // Verbose
853       if (!logIsDefault() && (!logIsInfo() || logIsDebug())) {
854         fatal("--verbose is mutually exclusive with --debug and --quiet");
855       }
856       verbosity            = MSG_INFO;
857       logSetLogLevel(verbosity);
858     } else if (!idx--) {
859       // Version
860       message("ShellInABox version " VERSION " (revision " VCS_REVISION ")");
861       exit(0);
862     }
863   }
864   if (optind != argc) {
865     usage();
866     fatal("Failed to parse command line");
867   }
868   char *buf                = NULL;
869   check(argc >= 1);
870   for (int i = 0; i < argc; i++) {
871     buf                    = stringPrintf(buf, " %s", argv[i]);
872   }
873   info("Command line:%s", buf);
874   free(buf);
875
876   // If the user did not specify a port, use the default one
877   if (!cgi && !port) {
878     port                   = PORTNUM;
879   }
880
881   // If the user did not register any services, provide the default service
882   if (!getHashmapSize(serviceTable)) {
883     addToHashMap(serviceTable, "/", (char *)newService(":LOGIN"));
884   }
885   enumerateServices(serviceTable);
886   deleteHashMap(serviceTable);
887
888   // Do not allow non-root URLs for CGI operation
889   if (cgi) {
890     for (int i = 0; i < numServices; i++) {
891       if (strcmp(services[i]->path, "/")) {
892         fatal("Non-root service URLs are incompatible with CGI operation");
893       }
894     }
895     check(cgiSessionKey    = newSessionKey());
896   }
897
898   if (demonize) {
899     pid_t pid;
900     check((pid             = fork()) >= 0);
901     if (pid) {
902       _exit(0);
903     }
904     setsid();
905     if (pidfile) {
906 #ifndef O_LARGEFILE
907 #define O_LARGEFILE 0
908 #endif
909       int fd               = NOINTR(open(pidfile,
910                                          O_WRONLY|O_TRUNC|O_LARGEFILE|O_CREAT,
911                                          0644));
912       if (fd >= 0) {
913         char buf[40];
914         NOINTR(write(fd, buf, snprintf(buf, 40, "%d", (int)getpid())));
915         check(!NOINTR(close(fd)));
916       }
917     }
918   }
919   free((char *)pidfile);
920 }
921
922 static void removeLimits() {
923   static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
924   for (int i = 0; i < sizeof(res)/sizeof(int); i++) {
925     struct rlimit rl;
926     getrlimit(res[i], &rl);
927     if (rl.rlim_max < RLIM_INFINITY) {
928       rl.rlim_max  = RLIM_INFINITY;
929       setrlimit(res[i], &rl);
930       getrlimit(res[i], &rl);
931     }
932     if (rl.rlim_cur < rl.rlim_max) {
933       rl.rlim_cur  = rl.rlim_max;
934       setrlimit(res[i], &rl);
935     }
936   }
937 }
938
939 static void setUpSSL(Server *server) {
940   serverEnableSSL(server, enableSSL);
941
942   // Enable SSL support (if available)
943   if (enableSSL) {
944     check(serverSupportsSSL());
945     if (certificateFd >= 0) {
946       serverSetCertificateFd(server, certificateFd);
947     } else if (certificateDir) {
948       char *tmp;
949       if (strchr(certificateDir, '%')) {
950         fatal("Invalid certificate directory name \"%s\".", certificateDir);
951       }
952       check(tmp = stringPrintf(NULL, "%s/certificate%%s.pem", certificateDir));
953       serverSetCertificate(server, tmp, 1);
954       free(tmp);
955     } else {
956       serverSetCertificate(server, "certificate%s.pem", 1);
957     }
958   }
959 }
960
961 int main(int argc, char * const argv[]) {
962 #ifdef HAVE_SYS_PRCTL_H
963   // Disable core files
964   prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
965 #endif
966   struct rlimit rl = { 0 };
967   setrlimit(RLIMIT_CORE, &rl);
968   removeLimits();
969
970   // Parse command line arguments
971   parseArgs(argc, argv);
972
973   // Fork the launcher process, allowing us to drop privileges in the main
974   // process.
975   int launcherFd  = forkLauncher();
976
977   // Make sure that our timestamps will print in the standard format
978   setlocale(LC_TIME, "POSIX");
979
980   // Create a new web server
981   Server *server;
982   if (port) {
983     check(server  = newServer(localhostOnly, port));
984     dropPrivileges();
985     setUpSSL(server);
986   } else {
987     // For CGI operation we fork the new server, so that it runs in the
988     // background.
989     pid_t pid;
990     int   fds[2];
991     dropPrivileges();
992     check(!pipe(fds));
993     check((pid    = fork()) >= 0);
994     if (pid) {
995       // Wait for child to output initial HTML page
996       char wait;
997       check(!NOINTR(close(fds[1])));
998       check(!NOINTR(read(fds[0], &wait, 1)));
999       check(!NOINTR(close(fds[0])));
1000       _exit(0);
1001     }
1002     check(!NOINTR(close(fds[0])));
1003     check(server  = newCGIServer(localhostOnly, portMin, portMax,
1004                                  AJAX_TIMEOUT));
1005     cgiServer     = server;
1006     setUpSSL(server);
1007
1008     // Output a <frameset> that includes our root page
1009     check(port    = serverGetListeningPort(server));
1010     extern char cgiRootStart[];
1011     extern char cgiRootEnd[];
1012     char *cgiRoot;
1013     check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1));
1014     memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart);
1015     cgiRoot[cgiRootEnd - cgiRootStart] = '\000';
1016     printf("X-ShellInABox-Port: %d\r\n"
1017            "X-ShellInABox-Pid: %d\r\n"
1018            "Content-type: text/html; charset=utf-8\r\n\r\n",
1019            port, pid);
1020     printfUnchecked(cgiRoot, port, cgiSessionKey);
1021     fflush(stdout);
1022     free(cgiRoot);
1023     check(!NOINTR(close(fds[1])));
1024     closeAllFds((int []){ launcherFd, serverGetFd(server) }, 2);
1025     logSetLogLevel(MSG_QUIET);
1026   }
1027
1028   // Set log file format
1029   serverSetNumericHosts(server, numericHosts);
1030
1031   // Disable /quit handler
1032   serverRegisterHttpHandler(server, "/quit", NULL, NULL);
1033
1034   // Register HTTP handler(s)
1035   for (int i = 0; i < numServices; i++) {
1036     serverRegisterHttpHandler(server, services[i]->path,
1037                               shellInABoxHttpHandler, services[i]);
1038   }
1039
1040   // Register handlers for external files
1041   iterateOverHashMap(externalFiles, registerExternalFiles, server);
1042
1043   // Start the server
1044   serverLoop(server);
1045
1046   // Clean up
1047   deleteServer(server);
1048   finishAllSessions();
1049   deleteHashMap(externalFiles);
1050   for (int i = 0; i < numServices; i++) {
1051     deleteService(services[i]);
1052   }
1053   free(services);
1054   free(certificateDir);
1055   free(cgiSessionKey);
1056   info("Done");
1057   _exit(0);
1058 }
This page took 0.126771 seconds and 5 git commands to generate.