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' +
|
||||
'<frame src="' + url +
|
||||
document.location.search.replace(/^\?/, '') + '#' +
|
||||
encodeURIComponent(document.location.href) + '">\n' +
|
||||
encodeURIComponent(document.location.href) +
|
||||
',%s' + '">\n' +
|
||||
'</frameset>');
|
||||
})();
|
||||
--></script>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
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 {
|
||||
if (sessionKey && *sessionKey) {
|
||||
session = (struct Session *)getFromHashMap(sessions,
|
||||
sessionKey);
|
||||
}
|
||||
if (session) {
|
||||
*isNew = 0;
|
||||
deleteURL(session->url);
|
||||
session->url = url;
|
||||
} 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 new session: %s", sessionKey);
|
||||
} else {
|
||||
*isNew = 0;
|
||||
session = (struct Session *)getFromHashMap(sessions,
|
||||
sessionKey);
|
||||
if (session) {
|
||||
deleteURL(session->url);
|
||||
session->url = url;
|
||||
} else {
|
||||
debug("Failed to find session: %s", sessionKey);
|
||||
deleteURL(url);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.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,6 +179,7 @@ ShellInABox.prototype.sendRequest = function(request) {
|
|||
ShellInABox.prototype.onReadyStateChange = function(request) {
|
||||
if (request.readyState == XHR_LOADED) {
|
||||
if (request.status == 200) {
|
||||
this.connected = true;
|
||||
var response = eval('(' + request.responseText + ')');
|
||||
|
||||
if (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 {
|
||||
|
|
|
@ -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,35 +318,28 @@ 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset window dimensions of the pseudo TTY, if changed since last time set.
|
||||
if (session->width > 0 && session->height > 0 &&
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue