From 883b7aa7f01aaccb0558f97e64cf8e44fd6a7208 Mon Sep 17 00:00:00 2001 From: KLuka Date: Sun, 17 May 2015 19:49:53 +0200 Subject: [PATCH 1/2] Real IP recognition over proxy (partial fix #54) * Recogniton of HTTP header field 'X-Real-IP' was added. Value is used in LOGIN service with peer name as remote host identifier. This was we are able to see real IP in login related log files such as /var/log/auth.log, etc... * Real IP, peer name and URL are also passed to launched service as environment variables (SHELLINABOX_PEERNAME, SHELLINABOX_REALIP and SHELLINABOX_URL). This can be used by custom user service shell scripts or programs. * Real IP can also be passed to custom user service as command line parameter ${realip}. --- libhttp/http.h | 1 + libhttp/httpconnection.c | 4 ++ libhttp/httpconnection.h | 1 + shellinabox/launcher.c | 83 +++++++++++++++++++++++++++++++--------- shellinabox/launcher.h | 1 + 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/libhttp/http.h b/libhttp/http.h index 07e235d..c754a29 100644 --- a/libhttp/http.h +++ b/libhttp/http.h @@ -127,6 +127,7 @@ Server *httpGetServer(const HttpConnection *http); ServerConnection *httpGetServerConnection(const HttpConnection *); int httpGetFd(const HttpConnection *http); const char *httpGetPeerName(const HttpConnection *http); +const char *httpGetRealIP(const HttpConnection *http); const char *httpGetMethod(const HttpConnection *http); const char *httpGetVersion(const HttpConnection *http); const HashMap *httpGetHeaders(const HttpConnection *http); diff --git a/libhttp/httpconnection.c b/libhttp/httpconnection.c index 1bdc376..043a712 100644 --- a/libhttp/httpconnection.c +++ b/libhttp/httpconnection.c @@ -1889,6 +1889,10 @@ const char *httpGetPeerName(const struct HttpConnection *http) { return http->peerName; } +const char *httpGetRealIP(const struct HttpConnection *http) { + return getFromHashMap(&http->header, "x-real-ip"); +} + const char *httpGetMethod(const struct HttpConnection *http) { return http->method; } diff --git a/libhttp/httpconnection.h b/libhttp/httpconnection.h index eb503aa..f28513a 100644 --- a/libhttp/httpconnection.h +++ b/libhttp/httpconnection.h @@ -148,6 +148,7 @@ struct Server *httpGetServer(const struct HttpConnection *http); struct ServerConnection *httpGetServerConnection(const struct HttpConnection*); int httpGetFd(const HttpConnection *http); const char *httpGetPeerName(const struct HttpConnection *http); +const char *httpGetRealIP(const struct HttpConnection *http); const char *httpGetMethod(const struct HttpConnection *http); const char *httpGetProtocol(const struct HttpConnection *http); const char *httpGetHost(const struct HttpConnection *http); diff --git a/shellinabox/launcher.c b/shellinabox/launcher.c index 587fa0e..f5eba21 100644 --- a/shellinabox/launcher.c +++ b/shellinabox/launcher.c @@ -523,8 +523,12 @@ int launchChild(int service, struct Session *session, const char *url) { request->terminate = -1; request->width = session->width; request->height = session->height; - strncat(request->peerName, httpGetPeerName(session->http), - sizeof(request->peerName) - 1); + const char *peerName = httpGetPeerName(session->http); + strncat(request->peerName, peerName, sizeof(request->peerName) - 1); + const char *realIP = httpGetRealIP(session->http); + if (realIP && *realIP) { + strncat(request->realIP, realIP, sizeof(request->realIP) - 1); + } request->urlLength = strlen(u); memcpy(&request->url, u, request->urlLength); free(u); @@ -597,7 +601,7 @@ struct Utmp { static HashMap *childProcesses; void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath, - const char *peerName) { + const char *peerName, const char *realIP) { memset(utmp, 0, sizeof(struct Utmp)); utmp->pty = -1; utmp->useLogin = useLogin; @@ -609,7 +613,11 @@ void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath, strncat(&utmp->utmpx.ut_line[0], ptyPath + 5, sizeof(utmp->utmpx.ut_line) - 1); strncat(&utmp->utmpx.ut_id[0], ptyPath + 8, sizeof(utmp->utmpx.ut_id) - 1); strncat(&utmp->utmpx.ut_user[0], "SHELLINABOX", sizeof(utmp->utmpx.ut_user) - 1); - strncat(&utmp->utmpx.ut_host[0], peerName, sizeof(utmp->utmpx.ut_host) - 1); + char remoteHost[256]; + snprintf(remoteHost, 256, + (*realIP) ? "%s, %s" : "%s%s", peerName, + (*realIP) ? realIP : ""); + strncat(&utmp->utmpx.ut_host[0], remoteHost, sizeof(utmp->utmpx.ut_host) - 1); struct timeval tv; check(!gettimeofday(&tv, NULL)); utmp->utmpx.ut_tv.tv_sec = tv.tv_sec; @@ -618,10 +626,10 @@ void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath, } struct Utmp *newUtmp(int useLogin, const char *ptyPath, - const char *peerName) { + const char *peerName, const char *realIP) { struct Utmp *utmp; check(utmp = malloc(sizeof(struct Utmp))); - initUtmp(utmp, useLogin, ptyPath, peerName); + initUtmp(utmp, useLogin, ptyPath, peerName, realIP); return utmp; } @@ -776,7 +784,7 @@ static int ptsname_r(int fd, char *buf, size_t buflen) { #endif static int forkPty(int *pty, int useLogin, struct Utmp **utmp, - const char *peerName) { + const char *peerName, const char *realIP) { int slave; #ifdef HAVE_OPENPTY char* ptyPath = NULL; @@ -857,7 +865,7 @@ static int forkPty(int *pty, int useLogin, struct Utmp **utmp, #endif // Fill in utmp entry - *utmp = newUtmp(useLogin, ptyPath, peerName); + *utmp = newUtmp(useLogin, ptyPath, peerName, realIP); // Now, fork off the child process pid_t pid; @@ -1250,7 +1258,8 @@ static void destroyVariableHashEntry(void *arg ATTR_UNUSED, char *key, static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED, struct Service *service, const char *peerName, - char **environment, const char *url) { + const char *realIP, char **environment, + const char *url) { UNUSED(width); UNUSED(height); @@ -1287,6 +1296,9 @@ static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED, check(key = strdup("peer")); check(value = strdup(peerName)); addToHashMap(vars, key, value); + check(key = strdup("realip")); + check(value = strdup(realIP)); + addToHashMap(vars, key, value); check(key = strdup("uid")); addToHashMap(vars, key, stringPrintf(NULL, "%d", service->uid)); check(key = strdup("url")); @@ -1476,7 +1488,7 @@ void setWindowSize(int pty, int width, int height) { } static void childProcess(struct Service *service, int width, int height, - struct Utmp *utmp, const char *peerName, + struct Utmp *utmp, const char *peerName, const char *realIP, const char *url) { // Set initial window size setWindowSize(0, width, height); @@ -1504,6 +1516,18 @@ static void childProcess(struct Service *service, int width, int height, legalEnv[i], value); } } + + // Add useful environment variables that can be used in custom client scripts + // or programs. + numEnvVars += 3; + check(environment = realloc(environment, + (numEnvVars + 1)*sizeof(char *))); + environment[numEnvVars-3] = stringPrintf(NULL, "SHELLINABOX_URL=%s", + url); + environment[numEnvVars-2] = stringPrintf(NULL, "SHELLINABOX_PEERNAME=%s", + peerName); + environment[numEnvVars-1] = stringPrintf(NULL, "SHELLINABOX_REALIP=%s", + realIP); environment[numEnvVars] = NULL; // Set initial terminal settings @@ -1603,12 +1627,20 @@ static void childProcess(struct Service *service, int width, int height, // Finally, launch the child process. if (service->useLogin == 1) { - execle("/bin/login", "login", "-p", "-h", peerName, + // At login service launch, we try to pass real IP in '-h' parameter. Real + // IP is provided in HTTP header field 'X-Real-IP', if ShellInABox is used + // behind properly configured HTTP proxy. + char remoteHost[256]; + snprintf(remoteHost, 256, + (*realIP) ? "%s, %s" : "%s%s", peerName, + (*realIP) ? realIP : ""); + execle("/bin/login", "login", "-p", "-h", remoteHost, (void *)0, environment); - execle("/usr/bin/login", "login", "-p", "-h", peerName, + execle("/usr/bin/login", "login", "-p", "-h", remoteHost, (void *)0, environment); } else { - execService(width, height, service, peerName, environment, url); + // Launch user provied service + execService(width, height, service, peerName, realIP, environment, url); } _exit(1); } @@ -1666,9 +1698,10 @@ static void launcherDaemon(int fd) { if (kill(request.terminate, SIGTERM) == 0) { debug("Terminating child %d (kill)", request.terminate); } else { - debug("Terminating child failed [%s]", strerror(errno)); + debug("Terminating child %d failed [%s]", request.terminate, + strerror(errno)); } - } + } continue; } @@ -1696,8 +1729,8 @@ static void launcherDaemon(int fd) { check(request.service >= 0); check(request.service < numServices); - // Sanitize the host name, so that we do not pass any unexpected characters - // to our child process. + // Sanitize peer name and real IP, so that we do not pass any unexpected + // characters to our child process. request.peerName[sizeof(request.peerName)-1] = '\000'; for (char *s = request.peerName; *s; s++) { if (!((*s >= '0' && *s <= '9') || @@ -1708,14 +1741,26 @@ static void launcherDaemon(int fd) { } } + request.realIP[sizeof(request.realIP)-1] = '\000'; + for (char *s = request.realIP; *s; s++) { + if (!((*s >= '0' && *s <= '9') || + (*s >= 'A' && *s <= 'Z') || + (*s >= 'a' && *s <= 'z') || + *s == '.' || *s == '-')) { + *s = '-'; + } + } + // Fork and exec the child process. int pty; struct Utmp *utmp; if ((pid = forkPty(&pty, services[request.service]->useLogin, - &utmp, request.peerName)) == 0) { + &utmp, + request.peerName, + request.realIP)) == 0) { childProcess(services[request.service], request.width, request.height, - utmp, request.peerName, url); + utmp, request.peerName, request.realIP, url); free(url); _exit(1); } else { diff --git a/shellinabox/launcher.h b/shellinabox/launcher.h index 2d7d145..64b7519 100644 --- a/shellinabox/launcher.h +++ b/shellinabox/launcher.h @@ -56,6 +56,7 @@ struct LaunchRequest { int width, height; pid_t terminate; char peerName[128]; + char realIP[128]; int urlLength; char url[0]; }; From 17dd88e8045cd1e33a576ded82edc6571f899c3f Mon Sep 17 00:00:00 2001 From: KLuka Date: Mon, 18 May 2015 16:26:38 +0200 Subject: [PATCH 2/2] Real IP recognition over proxy (man, help) * Manual page and output of --help parameter were updated according to changes in previous commit. --- shellinabox/shellinaboxd.c | 1 + shellinabox/shellinaboxd.man.in | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shellinabox/shellinaboxd.c b/shellinabox/shellinaboxd.c index bf44d2e..0df94bf 100644 --- a/shellinabox/shellinaboxd.c +++ b/shellinabox/shellinaboxd.c @@ -800,6 +800,7 @@ static void usage(void) { " ${home} - home directory\n" " ${lines} - number of rows\n" " ${peer} - name of remote peer\n" + " ${realip} - value of HTTP header field 'X-Real-IP'\n" " ${uid} - user id\n" " ${url} - the URL that serves the terminal session\n" " ${user} - user name\n" diff --git a/shellinabox/shellinaboxd.man.in b/shellinabox/shellinaboxd.man.in index 0e401a6..082b37e 100644 --- a/shellinabox/shellinaboxd.man.in +++ b/shellinabox/shellinaboxd.man.in @@ -449,6 +449,9 @@ number of rows. .B ${peer} name of remote peer. .TP +.B ${realip} +value of HTTP header field 'X-Real-IP'. +.TP .B ${uid} numeric user id. .TP @@ -458,11 +461,14 @@ the URL that serves the terminal session. .B ${user} user name. .P -Other than the default environment variables of +Other than the environment variables of .BR $TERM , -.B $COLUMNS +.B $COLUMNS, +.B $LINES, +.B $SHELLINABOX_PEERNAME, +.B $SHELLINABOX_REALIP and -.BR $LINES , +.BR $SHELLINABOX_URL, services can have environment variables passed to them, by preceding the with space separated variable assignments of the form .IR KEY = VALUE .