From 8dc6ebbfa649d66f884cfdc146cf4600158d1ea7 Mon Sep 17 00:00:00 2001 From: zodiac Date: Thu, 1 Jan 2009 23:16:32 +0000 Subject: [PATCH] Negotiate session keys early, allowing web servers to perform authentication when operating in CGI mode. git-svn-id: https://shellinabox.googlecode.com/svn/trunk@24 0da03de8-d603-11dd-86c2-0f8696b7b6f9 --- shellinabox/cgi_root.html | 3 +- shellinabox/launcher.c | 12 +++++++ shellinabox/launcher.h | 1 + shellinabox/session.c | 48 +++++++++++++++++++--------- shellinabox/session.h | 4 +++ shellinabox/shell_in_a_box.js | 15 ++++++--- shellinabox/shellinaboxd.c | 60 ++++++++++++++++++----------------- 7 files changed, 94 insertions(+), 49 deletions(-) diff --git a/shellinabox/cgi_root.html b/shellinabox/cgi_root.html index edac070..22155ca 100644 --- a/shellinabox/cgi_root.html +++ b/shellinabox/cgi_root.html @@ -61,7 +61,8 @@ document.write('\n' + '\n' + + encodeURIComponent(document.location.href) + + ',%s' + '">\n' + ''); })(); --> diff --git a/shellinabox/launcher.c b/shellinabox/launcher.c index d393276..6542124 100644 --- a/shellinabox/launcher.c +++ b/shellinabox/launcher.c @@ -179,6 +179,11 @@ int supportsPAM(void) { } int launchChild(int service, struct Session *session) { + if (launcher < 0) { + errno = EINVAL; + return -1; + } + struct LaunchRequest request = { .service = service, .width = session->width, @@ -1020,3 +1025,10 @@ int forkLauncher(void) { return launcher; } } + +void terminateLauncher(void) { + if (launcher >= 0) { + NOINTR(close(launcher)); + launcher = -1; + } +} diff --git a/shellinabox/launcher.h b/shellinabox/launcher.h index 9893715..e2ce632 100644 --- a/shellinabox/launcher.h +++ b/shellinabox/launcher.h @@ -61,6 +61,7 @@ int supportsPAM(void); int launchChild(int service, struct Session *session); void setWindowSize(int pty, int width, int height); int forkLauncher(void); +void terminateLauncher(void); void closeAllFds(int *exceptFd, int num); #endif diff --git a/shellinabox/session.c b/shellinabox/session.c index 604c823..790994f 100644 --- a/shellinabox/session.c +++ b/shellinabox/session.c @@ -137,6 +137,10 @@ void deleteSession(struct Session *session) { free(session); } +void abandonSession(struct Session *session) { + deleteFromHashMap(sessions, session->sessionKey); +} + void finishSession(struct Session *session) { deleteFromHashMap(sessions, session->sessionKey); } @@ -150,7 +154,7 @@ static void destroySessionHashEntry(void *arg, char *key, char *value) { deleteSession((struct Session *)value); } -static char *newSessionKey(void) { +char *newSessionKey(void) { int fd; check((fd = NOINTR(open("/dev/urandom", O_RDONLY))) >= 0); unsigned char buf[16]; @@ -167,7 +171,7 @@ static char *newSessionKey(void) { drain: while (count >= 6) { *ptr++ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" - "ghijklmnopqrstuvwxyz0123456789+/" + "ghijklmnopqrstuvwxyz0123456789-/" [(bits >> (count -= 6)) & 0x3F]; } if (++i >= sizeof(buf)) { @@ -181,40 +185,54 @@ static char *newSessionKey(void) { } } *ptr = '\000'; - check(!getFromHashMap(sessions, sessionKey)); + check(!sessions || !getFromHashMap(sessions, sessionKey)); return sessionKey; } -struct Session *findSession(int *isNew, HttpConnection *http, URL *url) { +struct Session *findCGISession(int *isNew, HttpConnection *http, URL *url, + const char *cgiSessionKey) { *isNew = 1; if (!sessions) { sessions = newHashMap(destroySessionHashEntry, NULL); } const HashMap *args = urlGetArgs(url); const char *sessionKey = getFromHashMap(args, "session"); - struct Session *session; - if (!sessionKey || !*sessionKey) { - // Caller did not know the session key, yet. Create a new one. - check(sessionKey = newSessionKey()); - session = newSession(sessionKey, httpGetServer(http), url, - httpGetPeerName(http)); - addToHashMap(sessions, sessionKey, (const char *)session); - debug("Creating new session: %s", sessionKey); + struct Session *session= NULL; + if (cgiSessionKey && + (!sessionKey || strcmp(cgiSessionKey, sessionKey))) { + // In CGI mode, we only ever allow exactly one session with a + // pre-negotiated key. + deleteURL(url); } else { - *isNew = 0; - session = (struct Session *)getFromHashMap(sessions, + if (sessionKey && *sessionKey) { + session = (struct Session *)getFromHashMap(sessions, sessionKey); + } if (session) { + *isNew = 0; deleteURL(session->url); session->url = url; - } else { + } else if (!cgiSessionKey && sessionKey && *sessionKey) { + *isNew = 0; debug("Failed to find session: %s", sessionKey); deleteURL(url); + } else { + // First contact. Create session, now. + check(sessionKey = cgiSessionKey ? strdup(cgiSessionKey) + : newSessionKey()); + session = newSession(sessionKey, httpGetServer(http), url, + httpGetPeerName(http)); + addToHashMap(sessions, sessionKey, (const char *)session); + debug("Creating a new session: %s", sessionKey); } } return session; } +struct Session *findSession(int *isNew, HttpConnection *http, URL *url) { + return findCGISession(isNew, http, url, NULL); +} + void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){ iterateOverHashMap(sessions, fnc, arg); } diff --git a/shellinabox/session.h b/shellinabox/session.h index 2ac26c0..34c7797 100644 --- a/shellinabox/session.h +++ b/shellinabox/session.h @@ -73,8 +73,12 @@ struct Session *newSession(const char *sessionKey, Server *server, URL *url, const char *peerName); void destroySession(struct Session *session); void deleteSession(struct Session *session); +void abandonSession(struct Session *session); +char *newSessionKey(void); void finishSession(struct Session *session); void finishAllSessions(void); +struct Session *findCGISession(int *isNew, HttpConnection *http, URL *url, + const char *cgiSessionKey); struct Session *findSession(int *isNew, HttpConnection *http, URL *url); void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg); int numSessions(void); diff --git a/shellinabox/shell_in_a_box.js b/shellinabox/shell_in_a_box.js index db75307..c096093 100644 --- a/shellinabox/shell_in_a_box.js +++ b/shellinabox/shell_in_a_box.js @@ -96,14 +96,17 @@ function ShellInABox(url, container) { this.url = url; } if (document.location.hash != '') { - this.nextUrl = decodeURIComponent(document.location.hash). + var hash = decodeURIComponent(document.location.hash). replace(/^#/, ''); + this.nextUrl = hash.replace(/,.*/, ''); + this.session = hash.replace(/[^,]*,/, ''); } else { this.nextUrl = this.url; + this.session = null; } - this.session = null; this.pendingKeys = ''; this.keysInFlight = false; + this.connected = false; this.superClass.constructor.call(this, container); // We have to initiate the first XMLHttpRequest from a timer. Otherwise, @@ -117,6 +120,7 @@ function ShellInABox(url, container) { extend(ShellInABox, VT100); ShellInABox.prototype.sessionClosed = function() { + this.connected = false; if (this.session) { this.session = undefined; if (this.cursorX > 0) { @@ -175,7 +179,8 @@ ShellInABox.prototype.sendRequest = function(request) { ShellInABox.prototype.onReadyStateChange = function(request) { if (request.readyState == XHR_LOADED) { if (request.status == 200) { - var response = eval('(' + request.responseText + ')'); + this.connected = true; + var response = eval('(' + request.responseText + ')'); if (response.data) { this.vt100(response.data); @@ -190,7 +195,6 @@ ShellInABox.prototype.onReadyStateChange = function(request) { } } else if (request.status == 0) { // Time Out - this.inspect(request);/***/ this.sendRequest(request); } else { this.sessionClosed(); @@ -199,6 +203,9 @@ ShellInABox.prototype.onReadyStateChange = function(request) { }; ShellInABox.prototype.sendKeys = function(keys) { + if (!this.connected) { + return; + } if (this.keysInFlight || this.session == undefined) { this.pendingKeys += keys; } else { diff --git a/shellinabox/shellinaboxd.c b/shellinabox/shellinaboxd.c index 67ade66..6ea4702 100644 --- a/shellinabox/shellinaboxd.c +++ b/shellinabox/shellinaboxd.c @@ -79,7 +79,8 @@ static int enableSSL = 1; static char *certificateDir; static HashMap *externalFiles; static Server *cgiServer; - +static char *cgiSessionKey; +static int cgiSessions; static char *jsonEscape(const char *buf, int len) { static const char *hexDigit = "0123456789ABCDEF"; @@ -290,16 +291,13 @@ static int dataHandler(HttpConnection *http, struct Service *service, // Find an existing session, or create the record for a new one int isNew; - struct Session *session = findSession(&isNew, http, url); + struct Session *session = findCGISession(&isNew, http, url, cgiSessionKey); if (session == NULL) { - if (cgiServer && numSessions() == 0) { - // CGI servers only ever serve a single session - serverExitLoop(cgiServer, 1); - } httpSendReply(http, 400, "Bad Request", NULL); return HTTP_DONE; } + // Sanity check if (!isNew && strcmp(session->peerName, httpGetPeerName(http))) { error("Peername changed from %s to %s", session->peerName, httpGetPeerName(http)); @@ -320,34 +318,27 @@ static int dataHandler(HttpConnection *http, struct Service *service, session->height = atoi(height); } - // If the caller provided the "keys" parameter, this request sends key - // strokes but does not expect a reply. - if (!keys) { - if (session->http && - !completePendingRequest(session, "", 0, MAX_RESPONSE)) { + // Create a new session, if the client did not provide an existing one + if (isNew) { + if (cgiServer && cgiSessions++) { + serverExitLoop(cgiServer, 1); + abandonSession(session); httpSendReply(http, 400, "Bad Request", NULL); return HTTP_DONE; } session->http = http; - - // Create a new session, if the client did not provide an existing one - if (isNew) { - if (cgiServer && numSessions() > 1) { - deleteSession(session); - httpSendReply(http, 400, "Bad Request", NULL); - return HTTP_DONE; - } - debug("Creating new child process"); - if (launchChild(service->id, session) < 0) { - deleteSession(session); - httpSendReply(http, 500, "Internal Error", NULL); - return HTTP_DONE; - } - session->connection = serverAddConnection(httpGetServer(http), + if (launchChild(service->id, session) < 0) { + abandonSession(session); + httpSendReply(http, 500, "Internal Error", NULL); + return HTTP_DONE; + } + if (cgiServer) { + terminateLauncher(); + } + session->connection = serverAddConnection(httpGetServer(http), session->pty, handleSession, sessionDone, session); - serverSetTimeout(session->connection, AJAX_TIMEOUT); - } + serverSetTimeout(session->connection, AJAX_TIMEOUT); } // Reset window dimensions of the pseudo TTY, if changed since last time set. @@ -382,6 +373,15 @@ static int dataHandler(HttpConnection *http, struct Service *service, free(keyCodes); httpSendReply(http, 200, "OK", " "); return HTTP_DONE; + } else { + // This request is polling for data. Finish any pending requests and + // queue (or process) a new one. + if (session->http && session->http != http && + !completePendingRequest(session, "", 0, MAX_RESPONSE)) { + httpSendReply(http, 400, "Bad Request", NULL); + return HTTP_DONE; + } + session->http = http; } session->connection = serverGetConnection(session->server, @@ -798,6 +798,7 @@ static void parseArgs(int argc, char * const argv[]) { fatal("Non-root service URLs are incompatible with CGI operation"); } } + check(cgiSessionKey = newSessionKey()); } if (demonize) { @@ -885,7 +886,7 @@ int main(int argc, char * const argv[]) { check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1)); memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart); puts("Content-type: text/html; charset=utf-8\r\n\r"); - printf(cgiRoot, port); + printf(cgiRoot, port, cgiSessionKey); fflush(stdout); free(cgiRoot); check(!NOINTR(close(fds[1]))); @@ -937,6 +938,7 @@ int main(int argc, char * const argv[]) { } free(services); free(certificateDir); + free(cgiSessionKey); info("Done"); _exit(0); }