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