Skip to content

Commit

Permalink
Negotiate session keys early, allowing web servers to perform authent…
Browse files Browse the repository at this point in the history
…ication

when operating in CGI mode.


git-svn-id: https://shellinabox.googlecode.com/svn/trunk@24 0da03de8-d603-11dd-86c2-0f8696b7b6f9
  • Loading branch information
zodiac committed Jan 1, 2009
1 parent 60d6d84 commit 8dc6ebb
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 49 deletions.
3 changes: 2 additions & 1 deletion shellinabox/cgi_root.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
document.write('<frameset cols="*">\n' +
'<frame src="' + url +
document.location.search.replace(/^\?/, '') + '#' +
encodeURIComponent(document.location.href) + '">\n' +
encodeURIComponent(document.location.href) +
',%s' + '">\n' +
'</frameset>');
})();
--></script>
Expand Down
12 changes: 12 additions & 0 deletions shellinabox/launcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1020,3 +1025,10 @@ int forkLauncher(void) {
return launcher;
}
}

void terminateLauncher(void) {
if (launcher >= 0) {
NOINTR(close(launcher));
launcher = -1;
}
}
1 change: 1 addition & 0 deletions shellinabox/launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 33 additions & 15 deletions shellinabox/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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];
Expand All @@ -167,7 +171,7 @@ static char *newSessionKey(void) {
drain:
while (count >= 6) {
*ptr++ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
"ghijklmnopqrstuvwxyz0123456789+/"
"ghijklmnopqrstuvwxyz0123456789-/"
[(bits >> (count -= 6)) & 0x3F];
}
if (++i >= sizeof(buf)) {
Expand All @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions shellinabox/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 11 additions & 4 deletions shellinabox/shell_in_a_box.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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 {
Expand Down
60 changes: 31 additions & 29 deletions shellinabox/shellinaboxd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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));
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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])));
Expand Down Expand Up @@ -937,6 +938,7 @@ int main(int argc, char * const argv[]) {
}
free(services);
free(certificateDir);
free(cgiSessionKey);
info("Done");
_exit(0);
}

0 comments on commit 8dc6ebb

Please sign in to comment.