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
This commit is contained in:
zodiac 2009-01-01 23:16:32 +00:00
parent 60d6d8433a
commit 8dc6ebbfa6
7 changed files with 94 additions and 49 deletions

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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);

View file

@ -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 {

View file

@ -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);
}