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:
parent
60d6d8433a
commit
8dc6ebbfa6
7 changed files with 94 additions and 49 deletions
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue