Merge pull request #309 from KLuka/realip

Real IP recognition over proxy

* 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...

  Example for failed logins over nginx as can be seen in `/var/log/auth.log` file:
  ```
  May 17 20:17:20 luka-ubuntu login[9888]: FAILED LOGIN (1) on '/dev/pts/20' from '127.0.0.1, 192.168.1.100' FOR 'UNKNOWN', User not known to the underlying authentication module
  May 17 20:17:25 luka-ubuntu login[9888]: FAILED LOGIN (2) on '/dev/pts/20' from '127.0.0.1, 192.168.1.100' FOR 'luka', Authentication failure
  ```


* 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.

  ```
  SHELLINABOX_REALIP=192.168.1.100
  SHELLINABOX_URL=http://192.168.1.150:81/
  SHELLINABOX_PEERNAME=127.0.0.1
  ```

* Real IP can also be passed to custom user service as command line
  parameter ${realip}.

  See this example:
  ```
  ./shellinaboxd --service  '/:luka:luka:/:/home/luka/test.sh --peer ${peer} --realip ${realip}'
  ```
This commit is contained in:
Luka Krajger 2015-05-19 09:54:09 +02:00
commit ce25d2f2b1
7 changed files with 81 additions and 22 deletions

View file

@ -127,6 +127,7 @@ Server *httpGetServer(const HttpConnection *http);
ServerConnection *httpGetServerConnection(const HttpConnection *); ServerConnection *httpGetServerConnection(const HttpConnection *);
int httpGetFd(const HttpConnection *http); int httpGetFd(const HttpConnection *http);
const char *httpGetPeerName(const HttpConnection *http); const char *httpGetPeerName(const HttpConnection *http);
const char *httpGetRealIP(const HttpConnection *http);
const char *httpGetMethod(const HttpConnection *http); const char *httpGetMethod(const HttpConnection *http);
const char *httpGetVersion(const HttpConnection *http); const char *httpGetVersion(const HttpConnection *http);
const HashMap *httpGetHeaders(const HttpConnection *http); const HashMap *httpGetHeaders(const HttpConnection *http);

View file

@ -1889,6 +1889,10 @@ const char *httpGetPeerName(const struct HttpConnection *http) {
return http->peerName; return http->peerName;
} }
const char *httpGetRealIP(const struct HttpConnection *http) {
return getFromHashMap(&http->header, "x-real-ip");
}
const char *httpGetMethod(const struct HttpConnection *http) { const char *httpGetMethod(const struct HttpConnection *http) {
return http->method; return http->method;
} }

View file

@ -148,6 +148,7 @@ struct Server *httpGetServer(const struct HttpConnection *http);
struct ServerConnection *httpGetServerConnection(const struct HttpConnection*); struct ServerConnection *httpGetServerConnection(const struct HttpConnection*);
int httpGetFd(const HttpConnection *http); int httpGetFd(const HttpConnection *http);
const char *httpGetPeerName(const struct 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 *httpGetMethod(const struct HttpConnection *http);
const char *httpGetProtocol(const struct HttpConnection *http); const char *httpGetProtocol(const struct HttpConnection *http);
const char *httpGetHost(const struct HttpConnection *http); const char *httpGetHost(const struct HttpConnection *http);

View file

@ -523,8 +523,12 @@ int launchChild(int service, struct Session *session, const char *url) {
request->terminate = -1; request->terminate = -1;
request->width = session->width; request->width = session->width;
request->height = session->height; request->height = session->height;
strncat(request->peerName, httpGetPeerName(session->http), const char *peerName = httpGetPeerName(session->http);
sizeof(request->peerName) - 1); 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); request->urlLength = strlen(u);
memcpy(&request->url, u, request->urlLength); memcpy(&request->url, u, request->urlLength);
free(u); free(u);
@ -597,7 +601,7 @@ struct Utmp {
static HashMap *childProcesses; static HashMap *childProcesses;
void initUtmp(struct Utmp *utmp, int useLogin, const char *ptyPath, 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)); memset(utmp, 0, sizeof(struct Utmp));
utmp->pty = -1; utmp->pty = -1;
utmp->useLogin = useLogin; 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_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_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_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; struct timeval tv;
check(!gettimeofday(&tv, NULL)); check(!gettimeofday(&tv, NULL));
utmp->utmpx.ut_tv.tv_sec = tv.tv_sec; 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, struct Utmp *newUtmp(int useLogin, const char *ptyPath,
const char *peerName) { const char *peerName, const char *realIP) {
struct Utmp *utmp; struct Utmp *utmp;
check(utmp = malloc(sizeof(struct Utmp))); check(utmp = malloc(sizeof(struct Utmp)));
initUtmp(utmp, useLogin, ptyPath, peerName); initUtmp(utmp, useLogin, ptyPath, peerName, realIP);
return utmp; return utmp;
} }
@ -776,7 +784,7 @@ static int ptsname_r(int fd, char *buf, size_t buflen) {
#endif #endif
static int forkPty(int *pty, int useLogin, struct Utmp **utmp, static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
const char *peerName) { const char *peerName, const char *realIP) {
int slave; int slave;
#ifdef HAVE_OPENPTY #ifdef HAVE_OPENPTY
char* ptyPath = NULL; char* ptyPath = NULL;
@ -857,7 +865,7 @@ static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
#endif #endif
// Fill in utmp entry // Fill in utmp entry
*utmp = newUtmp(useLogin, ptyPath, peerName); *utmp = newUtmp(useLogin, ptyPath, peerName, realIP);
// Now, fork off the child process // Now, fork off the child process
pid_t pid; 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, static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED,
struct Service *service, const char *peerName, struct Service *service, const char *peerName,
char **environment, const char *url) { const char *realIP, char **environment,
const char *url) {
UNUSED(width); UNUSED(width);
UNUSED(height); UNUSED(height);
@ -1287,6 +1296,9 @@ static void execService(int width ATTR_UNUSED, int height ATTR_UNUSED,
check(key = strdup("peer")); check(key = strdup("peer"));
check(value = strdup(peerName)); check(value = strdup(peerName));
addToHashMap(vars, key, value); addToHashMap(vars, key, value);
check(key = strdup("realip"));
check(value = strdup(realIP));
addToHashMap(vars, key, value);
check(key = strdup("uid")); check(key = strdup("uid"));
addToHashMap(vars, key, stringPrintf(NULL, "%d", service->uid)); addToHashMap(vars, key, stringPrintf(NULL, "%d", service->uid));
check(key = strdup("url")); 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, 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) { const char *url) {
// Set initial window size // Set initial window size
setWindowSize(0, width, height); setWindowSize(0, width, height);
@ -1504,6 +1516,18 @@ static void childProcess(struct Service *service, int width, int height,
legalEnv[i], value); 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; environment[numEnvVars] = NULL;
// Set initial terminal settings // Set initial terminal settings
@ -1603,12 +1627,20 @@ static void childProcess(struct Service *service, int width, int height,
// Finally, launch the child process. // Finally, launch the child process.
if (service->useLogin == 1) { 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); (void *)0, environment);
execle("/usr/bin/login", "login", "-p", "-h", peerName, execle("/usr/bin/login", "login", "-p", "-h", remoteHost,
(void *)0, environment); (void *)0, environment);
} else { } else {
execService(width, height, service, peerName, environment, url); // Launch user provied service
execService(width, height, service, peerName, realIP, environment, url);
} }
_exit(1); _exit(1);
} }
@ -1666,7 +1698,8 @@ static void launcherDaemon(int fd) {
if (kill(request.terminate, SIGTERM) == 0) { if (kill(request.terminate, SIGTERM) == 0) {
debug("Terminating child %d (kill)", request.terminate); debug("Terminating child %d (kill)", request.terminate);
} else { } else {
debug("Terminating child failed [%s]", strerror(errno)); debug("Terminating child %d failed [%s]", request.terminate,
strerror(errno));
} }
} }
continue; continue;
@ -1696,8 +1729,8 @@ static void launcherDaemon(int fd) {
check(request.service >= 0); check(request.service >= 0);
check(request.service < numServices); check(request.service < numServices);
// Sanitize the host name, so that we do not pass any unexpected characters // Sanitize peer name and real IP, so that we do not pass any unexpected
// to our child process. // characters to our child process.
request.peerName[sizeof(request.peerName)-1] = '\000'; request.peerName[sizeof(request.peerName)-1] = '\000';
for (char *s = request.peerName; *s; s++) { for (char *s = request.peerName; *s; s++) {
if (!((*s >= '0' && *s <= '9') || 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. // Fork and exec the child process.
int pty; int pty;
struct Utmp *utmp; struct Utmp *utmp;
if ((pid = forkPty(&pty, if ((pid = forkPty(&pty,
services[request.service]->useLogin, services[request.service]->useLogin,
&utmp, request.peerName)) == 0) { &utmp,
request.peerName,
request.realIP)) == 0) {
childProcess(services[request.service], request.width, request.height, childProcess(services[request.service], request.width, request.height,
utmp, request.peerName, url); utmp, request.peerName, request.realIP, url);
free(url); free(url);
_exit(1); _exit(1);
} else { } else {

View file

@ -56,6 +56,7 @@ struct LaunchRequest {
int width, height; int width, height;
pid_t terminate; pid_t terminate;
char peerName[128]; char peerName[128];
char realIP[128];
int urlLength; int urlLength;
char url[0]; char url[0];
}; };

View file

@ -800,6 +800,7 @@ static void usage(void) {
" ${home} - home directory\n" " ${home} - home directory\n"
" ${lines} - number of rows\n" " ${lines} - number of rows\n"
" ${peer} - name of remote peer\n" " ${peer} - name of remote peer\n"
" ${realip} - value of HTTP header field 'X-Real-IP'\n"
" ${uid} - user id\n" " ${uid} - user id\n"
" ${url} - the URL that serves the terminal session\n" " ${url} - the URL that serves the terminal session\n"
" ${user} - user name\n" " ${user} - user name\n"

View file

@ -449,6 +449,9 @@ number of rows.
.B ${peer} .B ${peer}
name of remote peer. name of remote peer.
.TP .TP
.B ${realip}
value of HTTP header field 'X-Real-IP'.
.TP
.B ${uid} .B ${uid}
numeric user id. numeric user id.
.TP .TP
@ -458,11 +461,14 @@ the URL that serves the terminal session.
.B ${user} .B ${user}
user name. user name.
.P .P
Other than the default environment variables of Other than the environment variables of
.BR $TERM , .BR $TERM ,
.B $COLUMNS .B $COLUMNS,
.B $LINES,
.B $SHELLINABOX_PEERNAME,
.B $SHELLINABOX_REALIP
and and
.BR $LINES , .BR $SHELLINABOX_URL,
services can have environment variables passed to them, by preceding services can have environment variables passed to them, by preceding
the <cmdline> with space separated variable assignments of the form the <cmdline> with space separated variable assignments of the form
.IR KEY = VALUE . .IR KEY = VALUE .