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' + document.write('<frameset cols="*">\n' +
'<frame src="' + url + '<frame src="' + url +
document.location.search.replace(/^\?/, '') + '#' + document.location.search.replace(/^\?/, '') + '#' +
encodeURIComponent(document.location.href) + '">\n' + encodeURIComponent(document.location.href) +
',%s' + '">\n' +
'</frameset>'); '</frameset>');
})(); })();
--></script> --></script>

View file

@ -179,6 +179,11 @@ int supportsPAM(void) {
} }
int launchChild(int service, struct Session *session) { int launchChild(int service, struct Session *session) {
if (launcher < 0) {
errno = EINVAL;
return -1;
}
struct LaunchRequest request = { struct LaunchRequest request = {
.service = service, .service = service,
.width = session->width, .width = session->width,
@ -1020,3 +1025,10 @@ int forkLauncher(void) {
return launcher; 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); int launchChild(int service, struct Session *session);
void setWindowSize(int pty, int width, int height); void setWindowSize(int pty, int width, int height);
int forkLauncher(void); int forkLauncher(void);
void terminateLauncher(void);
void closeAllFds(int *exceptFd, int num); void closeAllFds(int *exceptFd, int num);
#endif #endif

View file

@ -137,6 +137,10 @@ void deleteSession(struct Session *session) {
free(session); free(session);
} }
void abandonSession(struct Session *session) {
deleteFromHashMap(sessions, session->sessionKey);
}
void finishSession(struct Session *session) { void finishSession(struct Session *session) {
deleteFromHashMap(sessions, session->sessionKey); deleteFromHashMap(sessions, session->sessionKey);
} }
@ -150,7 +154,7 @@ static void destroySessionHashEntry(void *arg, char *key, char *value) {
deleteSession((struct Session *)value); deleteSession((struct Session *)value);
} }
static char *newSessionKey(void) { char *newSessionKey(void) {
int fd; int fd;
check((fd = NOINTR(open("/dev/urandom", O_RDONLY))) >= 0); check((fd = NOINTR(open("/dev/urandom", O_RDONLY))) >= 0);
unsigned char buf[16]; unsigned char buf[16];
@ -167,7 +171,7 @@ static char *newSessionKey(void) {
drain: drain:
while (count >= 6) { while (count >= 6) {
*ptr++ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" *ptr++ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
"ghijklmnopqrstuvwxyz0123456789+/" "ghijklmnopqrstuvwxyz0123456789-/"
[(bits >> (count -= 6)) & 0x3F]; [(bits >> (count -= 6)) & 0x3F];
} }
if (++i >= sizeof(buf)) { if (++i >= sizeof(buf)) {
@ -181,40 +185,54 @@ static char *newSessionKey(void) {
} }
} }
*ptr = '\000'; *ptr = '\000';
check(!getFromHashMap(sessions, sessionKey)); check(!sessions || !getFromHashMap(sessions, sessionKey));
return 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; *isNew = 1;
if (!sessions) { if (!sessions) {
sessions = newHashMap(destroySessionHashEntry, NULL); sessions = newHashMap(destroySessionHashEntry, NULL);
} }
const HashMap *args = urlGetArgs(url); const HashMap *args = urlGetArgs(url);
const char *sessionKey = getFromHashMap(args, "session"); const char *sessionKey = getFromHashMap(args, "session");
struct Session *session; struct Session *session= NULL;
if (!sessionKey || !*sessionKey) { if (cgiSessionKey &&
// Caller did not know the session key, yet. Create a new one. (!sessionKey || strcmp(cgiSessionKey, sessionKey))) {
check(sessionKey = newSessionKey()); // In CGI mode, we only ever allow exactly one session with a
session = newSession(sessionKey, httpGetServer(http), url, // pre-negotiated key.
httpGetPeerName(http)); deleteURL(url);
addToHashMap(sessions, sessionKey, (const char *)session);
debug("Creating new session: %s", sessionKey);
} else { } else {
*isNew = 0; if (sessionKey && *sessionKey) {
session = (struct Session *)getFromHashMap(sessions, session = (struct Session *)getFromHashMap(sessions,
sessionKey); sessionKey);
}
if (session) { if (session) {
*isNew = 0;
deleteURL(session->url); deleteURL(session->url);
session->url = url; session->url = url;
} else { } else if (!cgiSessionKey && sessionKey && *sessionKey) {
*isNew = 0;
debug("Failed to find session: %s", sessionKey); debug("Failed to find session: %s", sessionKey);
deleteURL(url); 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; 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){ void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){
iterateOverHashMap(sessions, fnc, arg); iterateOverHashMap(sessions, fnc, arg);
} }

View file

@ -73,8 +73,12 @@ struct Session *newSession(const char *sessionKey, Server *server, URL *url,
const char *peerName); const char *peerName);
void destroySession(struct Session *session); void destroySession(struct Session *session);
void deleteSession(struct Session *session); void deleteSession(struct Session *session);
void abandonSession(struct Session *session);
char *newSessionKey(void);
void finishSession(struct Session *session); void finishSession(struct Session *session);
void finishAllSessions(void); void finishAllSessions(void);
struct Session *findCGISession(int *isNew, HttpConnection *http, URL *url,
const char *cgiSessionKey);
struct Session *findSession(int *isNew, HttpConnection *http, URL *url); struct Session *findSession(int *isNew, HttpConnection *http, URL *url);
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg); void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg);
int numSessions(void); int numSessions(void);

View file

@ -96,14 +96,17 @@ function ShellInABox(url, container) {
this.url = url; this.url = url;
} }
if (document.location.hash != '') { if (document.location.hash != '') {
this.nextUrl = decodeURIComponent(document.location.hash). var hash = decodeURIComponent(document.location.hash).
replace(/^#/, ''); replace(/^#/, '');
this.nextUrl = hash.replace(/,.*/, '');
this.session = hash.replace(/[^,]*,/, '');
} else { } else {
this.nextUrl = this.url; this.nextUrl = this.url;
this.session = null;
} }
this.session = null;
this.pendingKeys = ''; this.pendingKeys = '';
this.keysInFlight = false; this.keysInFlight = false;
this.connected = false;
this.superClass.constructor.call(this, container); this.superClass.constructor.call(this, container);
// We have to initiate the first XMLHttpRequest from a timer. Otherwise, // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
@ -117,6 +120,7 @@ function ShellInABox(url, container) {
extend(ShellInABox, VT100); extend(ShellInABox, VT100);
ShellInABox.prototype.sessionClosed = function() { ShellInABox.prototype.sessionClosed = function() {
this.connected = false;
if (this.session) { if (this.session) {
this.session = undefined; this.session = undefined;
if (this.cursorX > 0) { if (this.cursorX > 0) {
@ -175,7 +179,8 @@ ShellInABox.prototype.sendRequest = function(request) {
ShellInABox.prototype.onReadyStateChange = function(request) { ShellInABox.prototype.onReadyStateChange = function(request) {
if (request.readyState == XHR_LOADED) { if (request.readyState == XHR_LOADED) {
if (request.status == 200) { if (request.status == 200) {
var response = eval('(' + request.responseText + ')'); this.connected = true;
var response = eval('(' + request.responseText + ')');
if (response.data) { if (response.data) {
this.vt100(response.data); this.vt100(response.data);
@ -190,7 +195,6 @@ ShellInABox.prototype.onReadyStateChange = function(request) {
} }
} else if (request.status == 0) { } else if (request.status == 0) {
// Time Out // Time Out
this.inspect(request);/***/
this.sendRequest(request); this.sendRequest(request);
} else { } else {
this.sessionClosed(); this.sessionClosed();
@ -199,6 +203,9 @@ ShellInABox.prototype.onReadyStateChange = function(request) {
}; };
ShellInABox.prototype.sendKeys = function(keys) { ShellInABox.prototype.sendKeys = function(keys) {
if (!this.connected) {
return;
}
if (this.keysInFlight || this.session == undefined) { if (this.keysInFlight || this.session == undefined) {
this.pendingKeys += keys; this.pendingKeys += keys;
} else { } else {

View file

@ -79,7 +79,8 @@ static int enableSSL = 1;
static char *certificateDir; static char *certificateDir;
static HashMap *externalFiles; static HashMap *externalFiles;
static Server *cgiServer; static Server *cgiServer;
static char *cgiSessionKey;
static int cgiSessions;
static char *jsonEscape(const char *buf, int len) { static char *jsonEscape(const char *buf, int len) {
static const char *hexDigit = "0123456789ABCDEF"; 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 // Find an existing session, or create the record for a new one
int isNew; int isNew;
struct Session *session = findSession(&isNew, http, url); struct Session *session = findCGISession(&isNew, http, url, cgiSessionKey);
if (session == NULL) { if (session == NULL) {
if (cgiServer && numSessions() == 0) {
// CGI servers only ever serve a single session
serverExitLoop(cgiServer, 1);
}
httpSendReply(http, 400, "Bad Request", NULL); httpSendReply(http, 400, "Bad Request", NULL);
return HTTP_DONE; return HTTP_DONE;
} }
// Sanity check
if (!isNew && strcmp(session->peerName, httpGetPeerName(http))) { if (!isNew && strcmp(session->peerName, httpGetPeerName(http))) {
error("Peername changed from %s to %s", error("Peername changed from %s to %s",
session->peerName, httpGetPeerName(http)); session->peerName, httpGetPeerName(http));
@ -320,34 +318,27 @@ static int dataHandler(HttpConnection *http, struct Service *service,
session->height = atoi(height); session->height = atoi(height);
} }
// If the caller provided the "keys" parameter, this request sends key // Create a new session, if the client did not provide an existing one
// strokes but does not expect a reply. if (isNew) {
if (!keys) { if (cgiServer && cgiSessions++) {
if (session->http && serverExitLoop(cgiServer, 1);
!completePendingRequest(session, "", 0, MAX_RESPONSE)) { abandonSession(session);
httpSendReply(http, 400, "Bad Request", NULL); httpSendReply(http, 400, "Bad Request", NULL);
return HTTP_DONE; return HTTP_DONE;
} }
session->http = http; session->http = http;
if (launchChild(service->id, session) < 0) {
// Create a new session, if the client did not provide an existing one abandonSession(session);
if (isNew) { httpSendReply(http, 500, "Internal Error", NULL);
if (cgiServer && numSessions() > 1) { return HTTP_DONE;
deleteSession(session); }
httpSendReply(http, 400, "Bad Request", NULL); if (cgiServer) {
return HTTP_DONE; terminateLauncher();
} }
debug("Creating new child process"); session->connection = serverAddConnection(httpGetServer(http),
if (launchChild(service->id, session) < 0) {
deleteSession(session);
httpSendReply(http, 500, "Internal Error", NULL);
return HTTP_DONE;
}
session->connection = serverAddConnection(httpGetServer(http),
session->pty, handleSession, session->pty, handleSession,
sessionDone, session); 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. // 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); free(keyCodes);
httpSendReply(http, 200, "OK", " "); httpSendReply(http, 200, "OK", " ");
return HTTP_DONE; 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, 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"); fatal("Non-root service URLs are incompatible with CGI operation");
} }
} }
check(cgiSessionKey = newSessionKey());
} }
if (demonize) { if (demonize) {
@ -885,7 +886,7 @@ int main(int argc, char * const argv[]) {
check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1)); check(cgiRoot = malloc(cgiRootEnd - cgiRootStart + 1));
memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart); memcpy(cgiRoot, cgiRootStart, cgiRootEnd - cgiRootStart);
puts("Content-type: text/html; charset=utf-8\r\n\r"); puts("Content-type: text/html; charset=utf-8\r\n\r");
printf(cgiRoot, port); printf(cgiRoot, port, cgiSessionKey);
fflush(stdout); fflush(stdout);
free(cgiRoot); free(cgiRoot);
check(!NOINTR(close(fds[1]))); check(!NOINTR(close(fds[1])));
@ -937,6 +938,7 @@ int main(int argc, char * const argv[]) {
} }
free(services); free(services);
free(certificateDir); free(certificateDir);
free(cgiSessionKey);
info("Done"); info("Done");
_exit(0); _exit(0);
} }