Merge branch 'feature/KASM-3527_http_headers' into 'master'

Resolve KASM-3527 "Feature/ http headers"

Closes KASM-3527

See merge request kasm-technologies/internal/KasmVNC!75
This commit is contained in:
Matthew McClaskey 2022-11-09 10:35:42 +00:00
commit d9b5b5db6a
3 changed files with 118 additions and 30 deletions

View File

@ -46,6 +46,9 @@ settings_t settings;
extern int wakeuppipe[2]; extern int wakeuppipe[2];
extern char *extra_headers;
extern unsigned extra_headers_len;
void traffic(const char * token) { void traffic(const char * token) {
/*if ((settings.verbose) && (! settings.daemon)) { /*if ((settings.verbose) && (! settings.daemon)) {
fprintf(stdout, "%s", token); fprintf(stdout, "%s", token);
@ -836,7 +839,8 @@ static void dirlisting(ws_ctx_t *ws_ctx, const char fullpath[], const char path[
"Location: %s/\r\n" "Location: %s/\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"\r\n", path); "%s"
"\r\n", path, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(301, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf)); weblog(301, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf));
return; return;
@ -850,7 +854,9 @@ static void dirlisting(ws_ctx_t *ws_ctx, const char fullpath[], const char path[
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/html\r\n" "Content-type: text/html\r\n"
"\r\n<html><title>Directory Listing</title><body><h2>%s</h2><hr><ul>", path); "%s"
"\r\n<html><title>Directory Listing</title><body><h2>%s</h2><hr><ul>",
extra_headers ? extra_headers : "", path);
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
unsigned totallen = strlen(buf); unsigned totallen = strlen(buf);
@ -937,8 +943,9 @@ static void servefile(ws_ctx_t *ws_ctx, const char *in, const char * const user,
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: %s\r\n" "Content-type: %s\r\n"
"Content-length: %lu\r\n" "Content-length: %lu\r\n"
"%s"
"\r\n", "\r\n",
name2mime(path), filesize); name2mime(path), filesize, extra_headers ? extra_headers : "");
const unsigned hdrlen = strlen(buf); const unsigned hdrlen = strlen(buf);
ws_send(ws_ctx, buf, hdrlen); ws_send(ws_ctx, buf, hdrlen);
@ -958,8 +965,9 @@ nope:
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"404"); "404", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(404, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf)); weblog(404, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf));
} }
@ -999,14 +1007,16 @@ notfound:
} }
static void send403(ws_ctx_t *ws_ctx, const char * const origip, const char * const ip) { static void send403(ws_ctx_t *ws_ctx, const char * const origip, const char * const ip) {
const char response[] = "HTTP/1.1 403 Forbidden\r\n" char buf[4096];
sprintf(buf, "HTTP/1.1 403 Forbidden\r\n"
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"403 Forbidden"; "403 Forbidden", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response)); ws_send(ws_ctx, buf, strlen(buf));
weblog(403, wsthread_handler_id, 0, origip, ip, "-", 1, "-", strlen(response)); weblog(403, wsthread_handler_id, 0, origip, ip, "-", 1, "-", strlen(buf));
} }
static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * const user, static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * const user,
@ -1079,8 +1089,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@ -1117,8 +1128,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@ -1171,8 +1183,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@ -1187,8 +1200,9 @@ nope:
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"400 Bad Request"); "400 Bad Request", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(400, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf)); weblog(400, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
return 1; return 1;
@ -1273,7 +1287,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: %u\r\n" "Content-length: %u\r\n"
"\r\n", len); "%s"
"\r\n", len, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, staging, len); ws_send(ws_ctx, staging, len);
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len);
@ -1286,7 +1301,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: image/jpeg\r\n" "Content-type: image/jpeg\r\n"
"Content-length: %u\r\n" "Content-length: %u\r\n"
"\r\n", len); "%s"
"\r\n", len, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, staging, len); ws_send(ws_ctx, staging, len);
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len);
@ -1356,8 +1372,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@ -1385,8 +1402,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@ -1440,8 +1458,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@ -1455,7 +1474,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: %lu\r\n" "Content-length: %lu\r\n"
"\r\n", strlen(statbuf)); "%s"
"\r\n", strlen(statbuf), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, statbuf, strlen(statbuf)); ws_send(ws_ctx, statbuf, strlen(statbuf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf));
@ -1471,7 +1491,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: %lu\r\n" "Content-length: %lu\r\n"
"\r\n", strlen(ptr)); "%s"
"\r\n", strlen(ptr), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, ptr, strlen(ptr)); ws_send(ws_ctx, ptr, strlen(ptr));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(ptr)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(ptr));
@ -1553,7 +1574,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: %lu\r\n" "Content-length: %lu\r\n"
"\r\n", strlen(statbuf)); "%s"
"\r\n", strlen(statbuf), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, statbuf, strlen(statbuf)); ws_send(ws_ctx, statbuf, strlen(statbuf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf));
@ -1568,8 +1590,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"Content-length: 6\r\n" "Content-length: 6\r\n"
"%s"
"\r\n" "\r\n"
"200 OK"); "200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@ -1585,8 +1608,9 @@ nope:
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"400 Bad Request"); "400 Bad Request", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(400, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(400, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
return 1; return 1;
@ -1596,8 +1620,9 @@ timeout:
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"503 Service Unavailable"); "503 Service Unavailable", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
weblog(503, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf)); weblog(503, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
return 1; return 1;
@ -1723,7 +1748,8 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
wserr("Authentication attempt failed, BasicAuth required, but client didn't send any\n"); wserr("Authentication attempt failed, BasicAuth required, but client didn't send any\n");
sprintf(response, "HTTP/1.1 401 Unauthorized\r\n" sprintf(response, "HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Basic realm=\"Websockify\"\r\n" "WWW-Authenticate: Basic realm=\"Websockify\"\r\n"
"\r\n"); "%s"
"\r\n", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response)); ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, "-", 1, url, strlen(response)); weblog(401, wsthread_handler_id, 0, origip, ip, "-", 1, url, strlen(response));
free_ws_ctx(ws_ctx); free_ws_ctx(ws_ctx);
@ -1805,7 +1831,8 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
wserr("Authentication attempt failed, wrong password for user %s\n", inuser); wserr("Authentication attempt failed, wrong password for user %s\n", inuser);
bl_addFailure(ip); bl_addFailure(ip);
sprintf(response, "HTTP/1.1 401 Forbidden\r\n" sprintf(response, "HTTP/1.1 401 Forbidden\r\n"
"\r\n"); "%s"
"\r\n", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response)); ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response)); weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response));
free_ws_ctx(ws_ctx); free_ws_ctx(ws_ctx);
@ -1829,8 +1856,9 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
"Server: KasmVNC/4.0\r\n" "Server: KasmVNC/4.0\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-type: text/plain\r\n" "Content-type: text/plain\r\n"
"%s"
"\r\n" "\r\n"
"401 Unauthorized"); "401 Unauthorized", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response)); ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response)); weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response));
goto done; goto done;

View File

@ -110,6 +110,11 @@ Run a mini-HTTP server which serves files from the given directory. Normally
the directory will contain the kasmweb client. It will use the websocket port. the directory will contain the kasmweb client. It will use the websocket port.
. .
.TP .TP
.B \-http-header \fIheader=val\fP
Append this header to all HTTP responses (file and API). May be given multiple
times.
.
.TP
.B \-rfbauth \fIpasswd-file\fP, \-PasswordFile \fIpasswd-file\fP .B \-rfbauth \fIpasswd-file\fP, \-PasswordFile \fIpasswd-file\fP
Password file for VNC authentication. There is no default, you should Password file for VNC authentication. There is no default, you should
specify the password file explicitly. Password file should be created with specify the password file explicitly. Password file should be created with

View File

@ -155,6 +155,49 @@ static char displayNumStr[16];
static int vncVerbose = DEFAULT_LOG_VERBOSITY; static int vncVerbose = DEFAULT_LOG_VERBOSITY;
char *extra_headers = NULL;
unsigned extra_headers_len = 0;
static unsigned
add_extra_headers(const char * const arg)
{
unsigned i, found = 0;
const char *sep;
for (i = 0; arg[i]; i++) {
if (arg[i] == '=') {
found++;
sep = &arg[i];
}
}
if (found != 1)
return 0;
const unsigned len = strlen(arg);
if (len < 3)
return 0;
if (arg[0] == '=' || arg[len - 1] == '=')
return 0;
extra_headers = realloc(extra_headers, extra_headers_len + 4 + len);
extra_headers[extra_headers_len] = '\0';
char tmp[len + 3];
const unsigned firstlen = sep - arg;
memcpy(tmp, arg, firstlen);
tmp[firstlen] = ':';
tmp[firstlen + 1] = ' ';
memcpy(&tmp[firstlen + 2], sep + 1, len - firstlen - 1);
tmp[len + 1] = '\r';
tmp[len + 2] = '\n';
memcpy(&extra_headers[extra_headers_len], tmp, len + 3);
extra_headers_len += len + 3;
extra_headers[extra_headers_len] = '\0';
return 1;
}
static void static void
vncPrintBanner(void) vncPrintBanner(void)
@ -319,6 +362,7 @@ void ddxUseMsg(void)
ErrorF("-depth D set screen 0's depth\n"); ErrorF("-depth D set screen 0's depth\n");
ErrorF("-pixelformat fmt set pixel format (rgbNNN or bgrNNN)\n"); ErrorF("-pixelformat fmt set pixel format (rgbNNN or bgrNNN)\n");
ErrorF("-inetd has been launched from inetd\n"); ErrorF("-inetd has been launched from inetd\n");
ErrorF("-http-header name=val append this header to all HTTP responses\n");
ErrorF("-noclipboard disable clipboard settings modification via vncconfig utility\n"); ErrorF("-noclipboard disable clipboard settings modification via vncconfig utility\n");
ErrorF("-verbose [n] verbose startup messages\n"); ErrorF("-verbose [n] verbose startup messages\n");
ErrorF("-quiet minimal startup messages\n"); ErrorF("-quiet minimal startup messages\n");
@ -530,6 +574,17 @@ ddxProcessArgument(int argc, char *argv[], int i)
return 2; return 2;
} }
if (strcmp(argv[i], "-http-header") == 0)
{
fail_unless_args(argc, i, 1);
++i;
if (!add_extra_headers(argv[i])) {
ErrorF("Invalid argument\n");
return 0;
}
return 2;
}
if (strcmp(argv[i], "-pixelformat") == 0) if (strcmp(argv[i], "-pixelformat") == 0)
{ {
char rgbbgr[4]; char rgbbgr[4];