From 3f6524ee307bcd2b46a4ccf037bcda315db37eb8 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Tue, 2 Mar 2021 15:23:32 +0200 Subject: [PATCH 01/12] Add support for owner screenshot HTTP GET API --- common/network/CMakeLists.txt | 1 + common/network/GetAPI.h | 54 ++++++++ common/network/GetAPIMessager.cxx | 183 ++++++++++++++++++++++++++ common/network/Socket.h | 4 + common/network/TcpSocket.cxx | 12 ++ common/network/TcpSocket.h | 4 + common/network/websocket.c | 146 ++++++++++++++++++++ common/network/websocket.h | 6 + common/rfb/EncodeManager.cxx | 2 +- common/rfb/VNCServerST.cxx | 7 +- common/rfb/VNCServerST.h | 5 + unix/xserver/hw/vnc/XserverDesktop.cc | 2 + 12 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 common/network/GetAPI.h create mode 100644 common/network/GetAPIMessager.cxx diff --git a/common/network/CMakeLists.txt b/common/network/CMakeLists.txt index 51a32a9..d63c696 100644 --- a/common/network/CMakeLists.txt +++ b/common/network/CMakeLists.txt @@ -1,6 +1,7 @@ include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd) set(NETWORK_SOURCES + GetAPIMessager.cxx Socket.cxx TcpSocket.cxx websocket.c diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h new file mode 100644 index 0000000..d0ae84e --- /dev/null +++ b/common/network/GetAPI.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2021 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __NETWORK_GET_API_H__ +#define __NETWORK_GET_API_H__ + +#include +#include +#include +#include +#include + +namespace network { + + class GetAPIMessager { + public: + GetAPIMessager(); + + // from main thread + void mainUpdateScreen(rfb::PixelBuffer *pb); + + // from network threads + uint8_t *netGetScreenshot(uint16_t w, uint16_t h, + const uint8_t q, const bool dedup, + uint32_t &len, uint8_t *staging); + private: + pthread_mutex_t screenMutex; + rfb::ManagedPixelBuffer screenPb; + uint16_t screenW, screenH; + uint64_t screenHash; + + std::vector cachedJpeg; + uint16_t cachedW, cachedH; + uint8_t cachedQ; + }; + +} + +#endif // __NETWORK_GET_API_H__ diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx new file mode 100644 index 0000000..6fe01a2 --- /dev/null +++ b/common/network/GetAPIMessager.cxx @@ -0,0 +1,183 @@ +/* Copyright (C) 2021 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#define __STDC_FORMAT_MACROS + +#include +#include +#include +#include +#include +#include +#include + +using namespace network; +using namespace rfb; + +static LogWriter vlog("GetAPIMessager"); + +PixelBuffer *progressiveBilinearScale(const PixelBuffer *pb, + const uint16_t tgtw, const uint16_t tgth, + const float tgtdiff); + +struct TightJPEGConfiguration { + int quality; + int subsampling; +}; + +static const struct TightJPEGConfiguration conf[10] = { + { 15, subsample4X }, // 0 + { 29, subsample4X }, // 1 + { 41, subsample4X }, // 2 + { 42, subsample2X }, // 3 + { 62, subsample2X }, // 4 + { 77, subsample2X }, // 5 + { 79, subsampleNone }, // 6 + { 86, subsampleNone }, // 7 + { 92, subsampleNone }, // 8 + { 100, subsampleNone } // 9 +}; + +GetAPIMessager::GetAPIMessager(): screenW(0), screenH(0), screenHash(0), + cachedW(0), cachedH(0), cachedQ(0) { + + pthread_mutex_init(&screenMutex, NULL); +} + +// from main thread +void GetAPIMessager::mainUpdateScreen(rfb::PixelBuffer *pb) { + if (pthread_mutex_trylock(&screenMutex)) + return; + + int stride; + const rdr::U8 * const buf = pb->getBuffer(pb->getRect(), &stride); + + if (pb->width() != screenW || pb->height() != screenH) { + screenHash = 0; + screenW = pb->width(); + screenH = pb->height(); + screenPb.setPF(pb->getPF()); + screenPb.setSize(screenW, screenH); + + cachedW = cachedH = cachedQ = 0; + cachedJpeg.clear(); + } + + const uint64_t newHash = XXH64(buf, pb->area() * 4, 0); + if (newHash != screenHash) { + cachedW = cachedH = cachedQ = 0; + cachedJpeg.clear(); + + screenHash = newHash; + rdr::U8 *rw = screenPb.getBufferRW(screenPb.getRect(), &stride); + memcpy(rw, buf, screenW * screenH * 4); + screenPb.commitBufferRW(screenPb.getRect()); + } + + pthread_mutex_unlock(&screenMutex); +} + +// from network threads +uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, + const uint8_t q, const bool dedup, + uint32_t &len, uint8_t *staging) { + + uint8_t *ret = NULL; + len = 0; + + if (w > screenW) + w = screenW; + if (h > screenH) + h = screenH; + + if (!w || !h || q > 9 || !staging) + return NULL; + + if (pthread_mutex_lock(&screenMutex)) + return NULL; + + if (w == cachedW && h == cachedH && q == cachedQ) { + if (dedup) { + // Return the hash of the unchanged image + sprintf((char *) staging, "%" PRIx64, screenHash); + ret = staging; + len = 16; + } else { + // Return the cached image + len = cachedJpeg.size(); + ret = staging; + memcpy(ret, &cachedJpeg[0], len); + + vlog.info("Returning cached screenshot"); + } + } else { + // Encode the new JPEG, cache it + JpegCompressor jc; + int quality, subsampling; + + quality = conf[q].quality; + subsampling = conf[q].subsampling; + + jc.clear(); + int stride; + + if (w != screenW || h != screenH) { + float xdiff = w / (float) screenW; + float ydiff = h / (float) screenH; + const float diff = xdiff < ydiff ? xdiff : ydiff; + + const uint16_t neww = screenW * diff; + const uint16_t newh = screenH * diff; + + const PixelBuffer *scaled = progressiveBilinearScale(&screenPb, neww, newh, diff); + const rdr::U8 * const buf = scaled->getBuffer(scaled->getRect(), &stride); + + jc.compress(buf, stride, scaled->getRect(), + scaled->getPF(), quality, subsampling); + + cachedJpeg.resize(jc.length()); + memcpy(&cachedJpeg[0], jc.data(), jc.length()); + + delete scaled; + + vlog.info("Returning scaled screenshot"); + } else { + const rdr::U8 * const buf = screenPb.getBuffer(screenPb.getRect(), &stride); + + jc.compress(buf, stride, screenPb.getRect(), + screenPb.getPF(), quality, subsampling); + + cachedJpeg.resize(jc.length()); + memcpy(&cachedJpeg[0], jc.data(), jc.length()); + + vlog.info("Returning normal screenshot"); + } + + cachedQ = q; + cachedW = w; + cachedH = h; + + len = cachedJpeg.size(); + ret = staging; + memcpy(ret, &cachedJpeg[0], len); + } + + pthread_mutex_unlock(&screenMutex); + + return ret; +} diff --git a/common/network/Socket.h b/common/network/Socket.h index bfda8a5..97cc4d0 100644 --- a/common/network/Socket.h +++ b/common/network/Socket.h @@ -74,6 +74,8 @@ namespace network { virtual ~ConnectionFilter() {} }; + class GetAPIMessager; + class SocketListener { public: SocketListener(int fd); @@ -93,6 +95,8 @@ namespace network { void setFilter(ConnectionFilter* f) {filter = f;} int getFd() {return fd;} + virtual GetAPIMessager *getMessager() { return NULL; } + protected: SocketListener(); diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index c6a7e14..db3aafa 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -42,6 +42,7 @@ #include #include "websocket.h" +#include #include #include #include @@ -431,6 +432,14 @@ int TcpListener::getMyPort() { extern settings_t settings; +static uint8_t *screenshotCb(void *messager, uint16_t w, uint16_t h, const uint8_t q, + const uint8_t dedup, + uint32_t *len, uint8_t *staging) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netGetScreenshot(w, h, q, dedup, *len, staging); +} + WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, @@ -515,6 +524,9 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.listen_sock = sock; + settings.messager = messager = new GetAPIMessager; + settings.screenshotCb = screenshotCb; + pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); } diff --git a/common/network/TcpSocket.h b/common/network/TcpSocket.h index 57a8629..dd98ce9 100644 --- a/common/network/TcpSocket.h +++ b/common/network/TcpSocket.h @@ -100,8 +100,12 @@ namespace network { int internalSocket; + virtual GetAPIMessager *getMessager() { return messager; } + protected: virtual Socket* createSocket(int fd); + private: + GetAPIMessager *messager; }; void createLocalTcpListeners(std::list *listeners, diff --git a/common/network/websocket.c b/common/network/websocket.c index 21e22ee..6759a09 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -9,6 +9,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -83,6 +84,32 @@ int resolve_host(struct in_addr *sin_addr, const char *hostname) return 0; } +static const char *parse_get(const char * const in, const char * const opt, unsigned *len) { + const char *start = in; + const char *end = strchrnul(start, '&'); + const unsigned optlen = strlen(opt); + *len = 0; + + while (1) { + if (!strncmp(start, opt, optlen)) { + const char *arg = strchr(start, '='); + if (!arg) + return ""; + arg++; + *len = end - arg; + return arg; + } + + if (!*end) + break; + + end++; + start = end; + end = strchrnul(start, '&'); + } + + return ""; +} /* * SSL Wrapper Code @@ -814,6 +841,116 @@ nope: ws_send(ws_ctx, buf, strlen(buf)); } +static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { + char buf[4096], path[4096], fullpath[4096], args[4096] = ""; + uint8_t ret = 0; // 0 = continue checking + + if (strncmp(in, "GET ", 4)) { + wserr("non-GET request, rejecting\n"); + return 0; + } + in += 4; + const char *end = strchr(in, ' '); + unsigned len = end - in; + + if (len < 1 || len > 1024 || strstr(in, "../")) { + wserr("Request too long (%u) or attempted dir traversal attack, rejecting\n", len); + return 0; + } + + end = memchr(in, '?', len); + if (end) { + len = end - in; + end++; + + const char *argend = strchr(end, ' '); + strncpy(args, end, argend - end); + args[argend - end] = '\0'; + } + + memcpy(path, in, len); + path[len] = '\0'; + + wserr("Requested owner api '%s' with args '%s'\n", path, args); + + #define entry(x) if (!strcmp(path, x)) + + const char *param; + + entry("/api/get_screenshot") { + uint8_t q = 7, dedup = 0; + uint16_t w = 4096, h = 4096; + + param = parse_get(args, "width", &len); + if (len && isdigit(param[0])) + w = atoi(param); + + param = parse_get(args, "height", &len); + if (len && isdigit(param[0])) + h = atoi(param); + + param = parse_get(args, "quality", &len); + if (len && isdigit(param[0])) + q = atoi(param); + + param = parse_get(args, "deduplicate", &len); + if (len && isalpha(param[0])) { + if (!strncmp(param, "true", len)) + dedup = 1; + } + + uint8_t *staging = malloc(1024 * 1024 * 8); + + settings.screenshotCb(settings.messager, w, h, q, dedup, &len, staging); + + if (len == 16) { + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: %u\r\n" + "\r\n", len); + ws_send(ws_ctx, buf, strlen(buf)); + ws_send(ws_ctx, staging, len); + + wserr("Screenshot hadn't changed and dedup was requested, sent hash\n"); + ret = 1; + } else if (len) { + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: image/jpeg\r\n" + "Content-length: %u\r\n" + "\r\n", len); + ws_send(ws_ctx, buf, strlen(buf)); + ws_send(ws_ctx, staging, len); + + wserr("Sent screenshot %u bytes\n", len); + ret = 1; + } + + free(staging); + + if (!len) { + wserr("Invalid params to screenshot\n"); + goto nope; + } + } + + #undef entry + + return ret; +nope: + sprintf(buf, "HTTP/1.1 400 Bad Request\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "\r\n" + "400 Bad Request"); + ws_send(ws_ctx, buf, strlen(buf)); + return 1; +} + ws_ctx_t *do_handshake(int sock) { char handshake[4096], response[4096], sha1[29], trailer[17]; char *scheme, *pre; @@ -883,6 +1020,7 @@ ws_ctx_t *do_handshake(int sock) { } const char *colon; + unsigned char owner = 0; if ((colon = strchr(settings.basicauth, ':'))) { const char *hdr = strstr(handshake, "Authorization: Basic "); if (!hdr) { @@ -938,6 +1076,9 @@ ws_ctx_t *do_handshake(int sock) { snprintf(authbuf, 4096, "%s:%s", set->entries[i].user, set->entries[i].password); authbuf[4095] = '\0'; + + if (set->entries[i].owner) + owner = 1; break; } } @@ -978,9 +1119,14 @@ ws_ctx_t *do_handshake(int sock) { if (!parse_handshake(ws_ctx, handshake)) { handler_emsg("Invalid WS request, maybe a HTTP one\n"); + if (strstr(handshake, "/api/") && owner) + if (ownerapi(ws_ctx, handshake)) + goto done; + if (settings.httpdir && settings.httpdir[0]) servefile(ws_ctx, handshake); +done: free_ws_ctx(ws_ctx); return NULL; } diff --git a/common/network/websocket.h b/common/network/websocket.h index 3d757f1..abe214b 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -1,4 +1,5 @@ #include +#include #define BUFSIZE 65536 #define DBUFSIZE (BUFSIZE * 3) / 4 - 20 @@ -74,6 +75,11 @@ typedef struct { const char *passwdfile; int ssl_only; const char *httpdir; + + void *messager; + uint8_t *(*screenshotCb)(void *messager, uint16_t w, uint16_t h, const uint8_t q, + const uint8_t dedup, + uint32_t *len, uint8_t *staging); } settings_t; #ifdef __cplusplus diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index f5f088c..fb9b8a6 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -966,7 +966,7 @@ static PixelBuffer *bilinearScale(const PixelBuffer *pb, const uint16_t w, const return newpb; } -static PixelBuffer *progressiveBilinearScale(const PixelBuffer *pb, +PixelBuffer *progressiveBilinearScale(const PixelBuffer *pb, const uint16_t tgtw, const uint16_t tgth, const float tgtdiff) { diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 1ac8597..b999c51 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -51,6 +51,8 @@ #include #include +#include + #include #include #include @@ -91,7 +93,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) renderedCursorInvalid(false), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), lastConnectionTime(0), disableclients(false), - frameTimer(this) + frameTimer(this), apimessager(NULL) { lastUserInputTime = lastDisconnectTime = time(0); slog.debug("creating single-threaded server %s", name.buf); @@ -709,6 +711,9 @@ void VNCServerST::writeUpdate() } } + if (apimessager) + apimessager->mainUpdateScreen(pb); + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index ef6e3e0..2432cd4 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -43,6 +43,7 @@ namespace rfb { class ListConnInfo; class PixelBuffer; class KeyRemapper; + class network::GetAPIMessager; class VNCServerST : public VNCServer, public Timer::Callback, @@ -186,6 +187,8 @@ namespace rfb { bool getDisable() { return disableclients;}; void setDisable(bool disable) { disableclients = disable;}; + void setAPIMessager(network::GetAPIMessager *msgr) { apimessager = msgr; } + protected: friend class VNCSConnectionST; @@ -251,6 +254,8 @@ namespace rfb { Timer frameTimer; int inotifyfd; + + network::GetAPIMessager *apimessager; }; }; diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index de8bb78..c5706fe 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -86,6 +86,8 @@ XserverDesktop::XserverDesktop(int screenIndex_, i != listeners.end(); i++) { vncSetNotifyFd((*i)->getFd(), screenIndex, true, false); + if ((*i)->getMessager()) + server->setAPIMessager((*i)->getMessager()); } } From 980eedd33b00467ca6a1139027c0b60cca40331a Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 3 Mar 2021 14:55:14 +0200 Subject: [PATCH 02/12] Add HTTP GET APIs for creating, removing users and giving control --- common/network/GetAPI.h | 23 ++++- common/network/GetAPIMessager.cxx | 103 +++++++++++++++++++++- common/network/TcpSocket.cxx | 24 +++++- common/network/websocket.c | 137 +++++++++++++++++++++++++++++- common/network/websocket.h | 4 + common/rfb/VNCServerST.cxx | 70 ++++++++++++++- 6 files changed, 355 insertions(+), 6 deletions(-) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index d0ae84e..c3eae8e 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -19,6 +19,7 @@ #ifndef __NETWORK_GET_API_H__ #define __NETWORK_GET_API_H__ +#include #include #include #include @@ -29,7 +30,7 @@ namespace network { class GetAPIMessager { public: - GetAPIMessager(); + GetAPIMessager(const char *passwdfile_); // from main thread void mainUpdateScreen(rfb::PixelBuffer *pb); @@ -38,7 +39,27 @@ namespace network { uint8_t *netGetScreenshot(uint16_t w, uint16_t h, const uint8_t q, const bool dedup, uint32_t &len, uint8_t *staging); + uint8_t netAddUser(const char name[], const char pw[], const bool write); + uint8_t netRemoveUser(const char name[]); + uint8_t netGiveControlTo(const char name[]); + + enum USER_ACTION { + //USER_ADD, - handled locally for interactivity + USER_REMOVE, + USER_GIVE_CONTROL, + }; + + struct action_data { + enum USER_ACTION action; + kasmpasswd_entry_t data; + }; + + pthread_mutex_t userMutex; + std::vector actionQueue; + private: + const char *passwdfile; + pthread_mutex_t screenMutex; rfb::ManagedPixelBuffer screenPb; uint16_t screenW, screenH; diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 6fe01a2..31eeb2b 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -53,10 +53,12 @@ static const struct TightJPEGConfiguration conf[10] = { { 100, subsampleNone } // 9 }; -GetAPIMessager::GetAPIMessager(): screenW(0), screenH(0), screenHash(0), +GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_), + screenW(0), screenH(0), screenHash(0), cachedW(0), cachedH(0), cachedQ(0) { pthread_mutex_init(&screenMutex, NULL); + pthread_mutex_init(&userMutex, NULL); } // from main thread @@ -181,3 +183,102 @@ uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, return ret; } + +#define USERNAME_LEN sizeof(((struct kasmpasswd_entry_t *)0)->user) +#define PASSWORD_LEN sizeof(((struct kasmpasswd_entry_t *)0)->password) + +uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const bool write) { + if (strlen(name) >= USERNAME_LEN) { + vlog.error("Username too long"); + return 0; + } + + if (strlen(pw) >= PASSWORD_LEN) { + vlog.error("Password too long"); + return 0; + } + + if (!passwdfile) + return 0; + + action_data act; + + memcpy(act.data.user, name, USERNAME_LEN); + act.data.user[USERNAME_LEN - 1] = '\0'; + memcpy(act.data.password, pw, PASSWORD_LEN); + act.data.password[PASSWORD_LEN - 1] = '\0'; + act.data.owner = 0; + act.data.write = write; + + if (pthread_mutex_lock(&userMutex)) + return 0; + + // This needs to be handled locally for proper interactivity + // (consider adding users when nobody is connected). + // The mutex and atomic rename keep things in sync. + + struct kasmpasswd_t *set = readkasmpasswd(passwdfile); + unsigned s; + for (s = 0; s < set->num; s++) { + if (!strcmp(set->entries[s].user, act.data.user)) { + vlog.error("Can't create user %s, already exists", act.data.user); + goto out; + } + } + + s = set->num++; + set->entries = (struct kasmpasswd_entry_t *) realloc(set->entries, + set->num * sizeof(struct kasmpasswd_entry_t)); + set->entries[s] = act.data; + + writekasmpasswd(passwdfile, set); + vlog.info("User %s created", act.data.user); +out: + pthread_mutex_unlock(&userMutex); + + return 1; +} + +uint8_t GetAPIMessager::netRemoveUser(const char name[]) { + if (strlen(name) >= USERNAME_LEN) { + vlog.error("Username too long"); + return 0; + } + + action_data act; + act.action = USER_REMOVE; + + memcpy(act.data.user, name, USERNAME_LEN); + act.data.user[USERNAME_LEN - 1] = '\0'; + + if (pthread_mutex_lock(&userMutex)) + return 0; + + actionQueue.push_back(act); + + pthread_mutex_unlock(&userMutex); + + return 1; +} + +uint8_t GetAPIMessager::netGiveControlTo(const char name[]) { + if (strlen(name) >= USERNAME_LEN) { + vlog.error("Username too long"); + return 0; + } + + action_data act; + act.action = USER_GIVE_CONTROL; + + memcpy(act.data.user, name, USERNAME_LEN); + act.data.user[USERNAME_LEN - 1] = '\0'; + + if (pthread_mutex_lock(&userMutex)) + return 0; + + actionQueue.push_back(act); + + pthread_mutex_unlock(&userMutex); + + return 1; +} diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index db3aafa..c3733c3 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -440,6 +440,25 @@ static uint8_t *screenshotCb(void *messager, uint16_t w, uint16_t h, const uint8 return msgr->netGetScreenshot(w, h, q, dedup, *len, staging); } +static uint8_t adduserCb(void *messager, const char name[], const char pw[], + const uint8_t write) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netAddUser(name, pw, write); +} + +static uint8_t removeCb(void *messager, const char name[]) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netRemoveUser(name); +} + +static uint8_t givecontrolCb(void *messager, const char name[]) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netGiveControlTo(name); +} + WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, @@ -524,8 +543,11 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.listen_sock = sock; - settings.messager = messager = new GetAPIMessager; + settings.messager = messager = new GetAPIMessager(settings.passwdfile); settings.screenshotCb = screenshotCb; + settings.adduserCb = adduserCb; + settings.removeCb = removeCb; + settings.givecontrolCb = givecontrolCb; pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); diff --git a/common/network/websocket.c b/common/network/websocket.c index 6759a09..422fd50 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -111,6 +111,29 @@ static const char *parse_get(const char * const in, const char * const opt, unsi return ""; } +static void percent_decode(const char *src, char *dst) { + while (1) { + if (!*src) + break; + if (*src != '%') { + *dst++ = *src++; + } else { + src++; + if (!src[0] || !src[1]) + break; + char hex[3]; + hex[0] = src[0]; + hex[1] = src[1]; + hex[2] = '\0'; + + src += 2; + *dst++ = strtol(hex, NULL, 16); + } + } + + *dst = '\0'; +} + /* * SSL Wrapper Code */ @@ -842,7 +865,7 @@ nope: } static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { - char buf[4096], path[4096], fullpath[4096], args[4096] = ""; + char buf[4096], path[4096], args[4096] = ""; uint8_t ret = 0; // 0 = continue checking if (strncmp(in, "GET ", 4)) { @@ -871,7 +894,11 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { memcpy(path, in, len); path[len] = '\0'; - wserr("Requested owner api '%s' with args '%s'\n", path, args); + if (strstr(args, "password=")) { + wserr("Requested owner api '%s' with args (skipped, includes password)\n", path); + } else { + wserr("Requested owner api '%s' with args '%s'\n", path, args); + } #define entry(x) if (!strcmp(path, x)) @@ -935,6 +962,112 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { wserr("Invalid params to screenshot\n"); goto nope; } + } else entry("/api/create_user") { + char decname[1024] = "", decpw[1024] = ""; + uint8_t write = 0; + + param = parse_get(args, "name", &len); + if (len) { + memcpy(buf, param, len); + buf[len] = '\0'; + percent_decode(buf, decname); + } + + param = parse_get(args, "password", &len); + if (len) { + memcpy(buf, param, len); + buf[len] = '\0'; + percent_decode(buf, decpw); + + struct crypt_data cdata; + cdata.initialized = 0; + + const char *encrypted = crypt_r(decpw, "$5$kasm$", &cdata); + strcpy(decpw, encrypted); + } + + param = parse_get(args, "write", &len); + if (len && isalpha(param[0])) { + if (!strncmp(param, "true", len)) + write = 1; + } + + if (!decname[0] || !decpw[0]) + goto nope; + + if (!settings.adduserCb(settings.messager, decname, decpw, write)) { + wserr("Invalid params to create_user\n"); + goto nope; + } + + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: 6\r\n" + "\r\n" + "200 OK"); + ws_send(ws_ctx, buf, strlen(buf)); + + ret = 1; + } else entry("/api/remove_user") { + char decname[1024] = ""; + + param = parse_get(args, "name", &len); + if (len) { + memcpy(buf, param, len); + buf[len] = '\0'; + percent_decode(buf, decname); + } + + if (!decname[0]) + goto nope; + + if (!settings.removeCb(settings.messager, decname)) { + wserr("Invalid params to remove_user\n"); + goto nope; + } + + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: 6\r\n" + "\r\n" + "200 OK"); + ws_send(ws_ctx, buf, strlen(buf)); + + wserr("Passed remove_user request to main thread\n"); + ret = 1; + } else entry("/api/give_control") { + char decname[1024] = ""; + + param = parse_get(args, "name", &len); + if (len) { + memcpy(buf, param, len); + buf[len] = '\0'; + percent_decode(buf, decname); + } + + if (!decname[0]) + goto nope; + + if (!settings.givecontrolCb(settings.messager, decname)) { + wserr("Invalid params to give_control\n"); + goto nope; + } + + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: 6\r\n" + "\r\n" + "200 OK"); + ws_send(ws_ctx, buf, strlen(buf)); + + wserr("Passed give_control request to main thread\n"); + ret = 1; } #undef entry diff --git a/common/network/websocket.h b/common/network/websocket.h index abe214b..e96fc95 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -80,6 +80,10 @@ typedef struct { uint8_t *(*screenshotCb)(void *messager, uint16_t w, uint16_t h, const uint8_t q, const uint8_t dedup, uint32_t *len, uint8_t *staging); + uint8_t (*adduserCb)(void *messager, const char name[], const char pw[], + const uint8_t write); + uint8_t (*removeCb)(void *messager, const char name[]); + uint8_t (*givecontrolCb)(void *messager, const char name[]); } settings_t; #ifdef __cplusplus diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index b999c51..a0b4998 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -641,6 +641,71 @@ int VNCServerST::msToNextUpdate() return frameTimer.getRemainingMs(); } +static void checkAPIMessages(network::GetAPIMessager *apimessager) +{ + if (pthread_mutex_lock(&apimessager->userMutex)) + return; + + const unsigned num = apimessager->actionQueue.size(); + unsigned i; + for (i = 0; i < num; i++) { + slog.info("Main thread processing user API request %u/%u", i + 1, num); + + const network::GetAPIMessager::action_data &act = apimessager->actionQueue[i]; + struct kasmpasswd_t *set = NULL; + unsigned s; + bool found; + + switch (act.action) { + case network::GetAPIMessager::USER_REMOVE: + set = readkasmpasswd(kasmpasswdpath); + found = false; + for (s = 0; s < set->num; s++) { + if (!strcmp(set->entries[s].user, act.data.user)) { + set->entries[s].user[0] = '\0'; + found = true; + break; + } + } + + if (found) { + writekasmpasswd(kasmpasswdpath, set); + slog.info("User %s removed", act.data.user); + } else { + slog.error("Tried to remove nonexistent user %s", act.data.user); + } + break; + case network::GetAPIMessager::USER_GIVE_CONTROL: + set = readkasmpasswd(kasmpasswdpath); + found = false; + for (s = 0; s < set->num; s++) { + if (!strcmp(set->entries[s].user, act.data.user)) { + set->entries[s].write = 1; + found = true; + } else { + set->entries[s].write = 0; + } + } + + if (found) { + writekasmpasswd(kasmpasswdpath, set); + slog.info("User %s given control", act.data.user); + } else { + slog.error("Tried to give control to nonexistent user %s", act.data.user); + } + break; + } + + if (set) { + free(set->entries); + free(set); + } + } + + apimessager->actionQueue.clear(); + pthread_mutex_unlock(&apimessager->userMutex); +} + // writeUpdate() is called on a regular interval in order to see what // updates are pending and propagates them to the update tracker for // each client. It uses the ComparingUpdateTracker's compare() method @@ -711,9 +776,12 @@ void VNCServerST::writeUpdate() } } - if (apimessager) + if (apimessager) { apimessager->mainUpdateScreen(pb); + checkAPIMessages(apimessager); + } + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; From 688e749fda5357e6d37d9d2522bf179381fe2768 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 3 Mar 2021 15:33:58 +0200 Subject: [PATCH 03/12] Tentative build fix for newer gccs --- common/network/GetAPIMessager.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 31eeb2b..4eafda5 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -25,6 +25,7 @@ #include #include #include +#include using namespace network; using namespace rfb; From 1269fddadf7a8b1acab9d488e578e122218cfa36 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 4 Mar 2021 13:43:05 +0200 Subject: [PATCH 04/12] Add a more specific error for a nonexistent screenshot --- common/network/GetAPIMessager.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 4eafda5..b06fabd 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -108,6 +108,9 @@ uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, if (h > screenH) h = screenH; + if (!screenW || !screenH) + vlog.error("Screenshot requested but no screenshot exists (screen hasn't been viewed)"); + if (!w || !h || q > 9 || !staging) return NULL; From fed991d6979769dce6c54cc4896b24e6d60b901c Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 4 Mar 2021 13:49:52 +0200 Subject: [PATCH 05/12] Return 401 unauth for non-owner trying /api/ --- common/network/websocket.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/common/network/websocket.c b/common/network/websocket.c index 422fd50..a035426 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -1252,9 +1252,23 @@ ws_ctx_t *do_handshake(int sock) { if (!parse_handshake(ws_ctx, handshake)) { handler_emsg("Invalid WS request, maybe a HTTP one\n"); - if (strstr(handshake, "/api/") && owner) - if (ownerapi(ws_ctx, handshake)) + if (strstr(handshake, "/api/")) { + handler_emsg("HTTP request under /api/\n"); + + if (owner) { + if (ownerapi(ws_ctx, handshake)) + goto done; + } else { + sprintf(response, "HTTP/1.1 401 Unauthorized\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "\r\n" + "401 Unauthorized"); + ws_send(ws_ctx, response, strlen(response)); goto done; + } + } if (settings.httpdir && settings.httpdir[0]) servefile(ws_ctx, handshake); From c3e30dcea19f3d527bbadbf21f49c6dae6d90584 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 15 Mar 2021 13:48:56 +0200 Subject: [PATCH 06/12] Add support for DLP_Region --- common/rfb/ServerCore.cxx | 4 + common/rfb/ServerCore.h | 1 + common/rfb/VNCServerST.cxx | 153 +++++++++++++++++++++++++++++++++++ common/rfb/VNCServerST.h | 8 ++ unix/xserver/hw/vnc/Xvnc.man | 6 ++ 5 files changed, 172 insertions(+) diff --git a/common/rfb/ServerCore.cxx b/common/rfb/ServerCore.cxx index 900a21f..94f1196 100644 --- a/common/rfb/ServerCore.cxx +++ b/common/rfb/ServerCore.cxx @@ -163,6 +163,10 @@ rfb::StringParameter rfb::Server::DLP_ClipLog ("DLP_Log", "Log clipboard/kbd actions. Accepts off, info or verbose", "off"); +rfb::StringParameter rfb::Server::DLP_Region +("DLP_Region", + "Black out anything outside this region", + ""); rfb::StringParameter rfb::Server::maxVideoResolution ("MaxVideoResolution", diff --git a/common/rfb/ServerCore.h b/common/rfb/ServerCore.h index bd97265..77314bd 100644 --- a/common/rfb/ServerCore.h +++ b/common/rfb/ServerCore.h @@ -49,6 +49,7 @@ namespace rfb { static IntParameter DLP_ClipDelay; static IntParameter DLP_KeyRateLimit; static StringParameter DLP_ClipLog; + static StringParameter DLP_Region; static IntParameter jpegVideoQuality; static IntParameter webpVideoQuality; static StringParameter maxVideoResolution; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index a0b4998..1e2a12f 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -85,6 +85,42 @@ extern rfb::StringParameter basicauth; // -=- Constructors/Destructor +static void mixedPercentages() { + slog.error("Mixing percentages and absolute values in DLP_Region is not allowed"); + exit(1); +} + +static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest, + char **inptr) { + char *nextptr, *ptr; + ptr = *inptr; + int val = strtol(ptr, &nextptr, 10); + if (!*ptr || ptr == nextptr) { + slog.error("Invalid value for DLP_Region"); + exit(1); + } + ptr = nextptr; + if (*ptr == '%') { + if (!percents) + mixedPercentages(); + pcdest = val; + + if (val < 0 || val > 100) { + slog.error("Percent must be 0-100"); + exit(1); + } + + ptr++; + } else if (percents) { + mixedPercentages(); + } + dest = val; + + for (; *ptr && *ptr == ','; ptr++); + + *inptr = ptr; +} + VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), blockCounter(0), pb(0), ledState(ledUnknown), @@ -98,6 +134,64 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) lastUserInputTime = lastDisconnectTime = time(0); slog.debug("creating single-threaded server %s", name.buf); + DLPRegion.enabled = DLPRegion.percents = false; + + if (Server::DLP_Region[0]) { + unsigned len = strlen(Server::DLP_Region); + unsigned i; + unsigned commas = 0; + int val; + char *ptr, *nextptr; + + for (i = 0; i < len; i++) { + if (Server::DLP_Region[i] == ',') + commas++; + } + + if (commas != 3) { + slog.error("DLP_Region must contain four values"); + exit(1); + } + + ptr = (char *) (const char *) Server::DLP_Region; + + val = strtol(ptr, &nextptr, 10); + if (!*ptr || ptr == nextptr) { + slog.error("Invalid value for DLP_Region"); + exit(1); + } + ptr = nextptr; + if (*ptr == '%') { + DLPRegion.percents = true; + DLPRegion.pcx1 = val; + ptr++; + } + DLPRegion.x1 = val; + + for (; *ptr && *ptr == ','; ptr++); + + parseRegionPart(DLPRegion.percents, DLPRegion.pcy1, DLPRegion.y1, + &ptr); + parseRegionPart(DLPRegion.percents, DLPRegion.pcx2, DLPRegion.x2, + &ptr); + parseRegionPart(DLPRegion.percents, DLPRegion.pcy2, DLPRegion.y2, + &ptr); + + // Validity checks + if (!DLPRegion.percents) { + if (DLPRegion.x1 > 0 && DLPRegion.x2 > 0 && DLPRegion.x2 <= DLPRegion.x1) { + slog.error("DLP_Region x2 must be > x1"); + exit(1); + } + if (DLPRegion.y1 > 0 && DLPRegion.y2 > 0 && DLPRegion.y2 <= DLPRegion.y1) { + slog.error("DLP_Region y2 must be > y1"); + exit(1); + } + } + + DLPRegion.enabled = 1; + } + kasmpasswdpath[0] = '\0'; wordexp_t wexp; if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD)) @@ -706,6 +800,62 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager) pthread_mutex_unlock(&apimessager->userMutex); } +void VNCServerST::blackOut() +{ + // Compute the region, since the resolution may have changed + rdr::U16 x1, y1, x2, y2; + + if (DLPRegion.percents) { + x1 = DLPRegion.pcx1 ? DLPRegion.pcx1 * pb->getRect().width() / 100 : 0; + y1 = DLPRegion.pcy1 ? DLPRegion.pcy1 * pb->getRect().height() / 100 : 0; + x2 = DLPRegion.pcx2 ? (100 - DLPRegion.pcx2) * pb->getRect().width() / 100 : pb->getRect().width(); + y2 = DLPRegion.pcy2 ? (100 - DLPRegion.pcy2) * pb->getRect().height() / 100 : pb->getRect().height(); + } else { + x1 = abs(DLPRegion.x1); + y1 = abs(DLPRegion.y1); + x2 = pb->getRect().width(); + y2 = pb->getRect().height(); + + if (DLPRegion.x2 < 0) + x2 += DLPRegion.x2; + else if (DLPRegion.x2 > 0) + x2 = DLPRegion.x2; + + if (DLPRegion.y2 < 0) + y2 += DLPRegion.y2; + else if (DLPRegion.y2 > 0) + y2 = DLPRegion.y2; + } + + if (y2 > pb->getRect().height()) + y2 = pb->getRect().height() - 1; + if (x2 > pb->getRect().width()) + x2 = pb->getRect().width() - 1; + + //slog.info("DLP_Region vals %u,%u %u,%u", x1, y1, x2, y2); + + ManagedPixelBuffer *mpb = (ManagedPixelBuffer *) pb; + int stride; + rdr::U8 *data = mpb->getBufferRW(mpb->getRect(), &stride); + stride *= 4; + + rdr::U16 y; + const rdr::U16 w = pb->getRect().width(); + const rdr::U16 h = pb->getRect().height(); + for (y = 0; y < h; y++) { + if (y < y1 || y > y2) { + memset(data, 0, stride); + } else { + if (x1) + memset(data, 0, x1 * 4); + if (x2) + memset(&data[x2 * 4], 0, (w - x2) * 4); + } + + data += stride; + } +} + // writeUpdate() is called on a regular interval in order to see what // updates are pending and propagates them to the update tracker for // each client. It uses the ComparingUpdateTracker's compare() method @@ -723,6 +873,9 @@ void VNCServerST::writeUpdate() assert(blockCounter == 0); assert(desktopStarted); + if (DLPRegion.enabled) + blackOut(); + comparer->getUpdateInfo(&ui, pb->getRect()); toCheck = ui.changed.union_(ui.copied); diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 2432cd4..b42f63c 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -235,6 +235,7 @@ namespace rfb { void stopFrameClock(); int msToNextUpdate(); void writeUpdate(); + void blackOut(); Region getPendingRegion(); const RenderedCursor* getRenderedCursor(); @@ -256,6 +257,13 @@ namespace rfb { int inotifyfd; network::GetAPIMessager *apimessager; + + struct { + bool enabled; + int x1, y1, x2, y2; + bool percents; + rdr::U16 pcx1, pcy1, pcx2, pcy2; + } DLPRegion; }; }; diff --git a/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man index f8ace17..299fe7d 100644 --- a/unix/xserver/hw/vnc/Xvnc.man +++ b/unix/xserver/hw/vnc/Xvnc.man @@ -280,6 +280,12 @@ Kasm exposes a few settings to the client the standard VNC does not. This param lets the server ignore those. . .TP +.B \-DLP_Region \fIx1,y1,x2,y2\fP +Black out anything outside this region. x1,y1 is the upper-left corner, +and x2,y2 the lower-left. In addition to absolute pixel values, percentages +are allowed, zero means "default", and a negative number means "border". +. +.TP .B \-DLP_ClipSendMax \fIbytes\fP Limit clipboard bytes to send to clients in one transaction. Default 10,000. 0 disables the limit, use \fBSendCutText\fP to disable clipboard sending entirely. From 46e03289bef185463c72e7cb502342757803ad13 Mon Sep 17 00:00:00 2001 From: matt Date: Mon, 22 Mar 2021 10:36:39 +0000 Subject: [PATCH 07/12] Manually merged region changes --- common/rfb/InputHandler.h | 4 +++- common/rfb/SMsgReader.cxx | 2 +- common/rfb/ServerCore.cxx | 9 +++++++++ common/rfb/ServerCore.h | 2 ++ common/rfb/VNCSConnectionST.cxx | 20 ++++++++++++++++++-- common/rfb/VNCSConnectionST.h | 2 +- common/rfb/VNCServerST.cxx | 27 ++++++++++++++++++++------- common/rfb/VNCServerST.h | 5 ++++- unix/xserver/hw/vnc/Input.c | 11 ++++++++++- unix/xserver/hw/vnc/Input.h | 3 ++- unix/xserver/hw/vnc/XserverDesktop.cc | 5 +++-- unix/xserver/hw/vnc/XserverDesktop.h | 3 ++- unix/xserver/hw/vnc/Xvnc.man | 8 ++++++++ 13 files changed, 83 insertions(+), 18 deletions(-) diff --git a/common/rfb/InputHandler.h b/common/rfb/InputHandler.h index 6c07284..806625f 100644 --- a/common/rfb/InputHandler.h +++ b/common/rfb/InputHandler.h @@ -36,7 +36,9 @@ namespace rfb { rdr::U32 __unused_attr keycode, bool __unused_attr down) { } virtual void pointerEvent(const Point& __unused_attr pos, - int __unused_attr buttonMask) { } + int __unused_attr buttonMask, + const bool __unused_attr skipClick, + const bool __unused_attr skipRelease) { } virtual void clientCutText(const char* __unused_attr str, int __unused_attr len) { } }; diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 7fb3f2c..a71272a 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -217,7 +217,7 @@ void SMsgReader::readPointerEvent() int mask = is->readU8(); int x = is->readU16(); int y = is->readU16(); - handler->pointerEvent(Point(x, y), mask); + handler->pointerEvent(Point(x, y), mask, false, false); } diff --git a/common/rfb/ServerCore.cxx b/common/rfb/ServerCore.cxx index 94f1196..3b36af0 100644 --- a/common/rfb/ServerCore.cxx +++ b/common/rfb/ServerCore.cxx @@ -168,6 +168,15 @@ rfb::StringParameter rfb::Server::DLP_Region "Black out anything outside this region", ""); +rfb::BoolParameter rfb::Server::DLP_RegionAllowClick +("DLP_RegionAllowClick", + "Allow clicks inside the blacked-out region", + false); +rfb::BoolParameter rfb::Server::DLP_RegionAllowRelease +("DLP_RegionAllowRelease", + "Allow click releases inside the blacked-out region", + true); + rfb::StringParameter rfb::Server::maxVideoResolution ("MaxVideoResolution", "When in video mode, downscale the screen to max this size.", diff --git a/common/rfb/ServerCore.h b/common/rfb/ServerCore.h index 77314bd..280596f 100644 --- a/common/rfb/ServerCore.h +++ b/common/rfb/ServerCore.h @@ -50,6 +50,8 @@ namespace rfb { static IntParameter DLP_KeyRateLimit; static StringParameter DLP_ClipLog; static StringParameter DLP_Region; + static BoolParameter DLP_RegionAllowClick; + static BoolParameter DLP_RegionAllowRelease; static IntParameter jpegVideoQuality; static IntParameter webpVideoQuality; static StringParameter maxVideoResolution; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 467e8f9..05f9282 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -637,7 +637,7 @@ void VNCSConnectionST::setPixelFormat(const PixelFormat& pf) setCursor(); } -void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask) +void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask, const bool skipClick, const bool skipRelease) { pointerEventTime = lastEventTime = time(0); server->lastUserInputTime = lastEventTime; @@ -649,7 +649,23 @@ void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask) server->pointerClient = this; else server->pointerClient = 0; - server->desktop->pointerEvent(pointerEventPos, buttonMask); + + bool skipclick = false, skiprelease = false; + if (server->DLPRegion.enabled) { + rdr::U16 x1, y1, x2, y2; + server->translateDLPRegion(x1, y1, x2, y2); + + if (pos.x < x1 || pos.x >= x2 || + pos.y < y1 || pos.y >= y2) { + + if (!Server::DLP_RegionAllowClick) + skipclick = true; + if (!Server::DLP_RegionAllowRelease) + skiprelease = true; + } + } + + server->desktop->pointerEvent(pointerEventPos, buttonMask, skipclick, skiprelease); } } diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 3eff9ad..d1e1267 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -168,7 +168,7 @@ namespace rfb { virtual void queryConnection(const char* userName); virtual void clientInit(bool shared); virtual void setPixelFormat(const PixelFormat& pf); - virtual void pointerEvent(const Point& pos, int buttonMask); + virtual void pointerEvent(const Point& pos, int buttonMask, const bool skipClick, const bool skipRelease); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void clientCutText(const char* str, int len); virtual void framebufferUpdateRequest(const Rect& r, bool incremental); diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 1e2a12f..5cd317a 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -123,7 +123,7 @@ static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest, VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), - blockCounter(0), pb(0), ledState(ledUnknown), + blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown), name(strDup(name_)), pointerClient(0), comparer(0), cursor(new Cursor(0, 0, Point(), NULL)), renderedCursorInvalid(false), @@ -800,11 +800,8 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager) pthread_mutex_unlock(&apimessager->userMutex); } -void VNCServerST::blackOut() +void VNCServerST::translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, rdr::U16 &y2) const { - // Compute the region, since the resolution may have changed - rdr::U16 x1, y1, x2, y2; - if (DLPRegion.percents) { x1 = DLPRegion.pcx1 ? DLPRegion.pcx1 * pb->getRect().width() / 100 : 0; y1 = DLPRegion.pcy1 ? DLPRegion.pcy1 * pb->getRect().height() / 100 : 0; @@ -834,11 +831,25 @@ void VNCServerST::blackOut() //slog.info("DLP_Region vals %u,%u %u,%u", x1, y1, x2, y2); - ManagedPixelBuffer *mpb = (ManagedPixelBuffer *) pb; +void VNCServerST::blackOut() +{ + // Compute the region, since the resolution may have changed + rdr::U16 x1, y1, x2, y2; + + translateDLPRegion(x1, y1, x2, y2); + + if (blackedpb) + delete blackedpb; + blackedpb = new ManagedPixelBuffer(pb->getPF(), pb->getRect().width(), pb->getRect().height()); + int stride; + const rdr::U8 *src = pb->getBuffer(pb->getRect(), &stride); + rdr::U8 *data = blackedpb->getBufferRW(pb->getRect(), &stride); rdr::U8 *data = mpb->getBufferRW(mpb->getRect(), &stride); stride *= 4; + memcpy(data, src, stride * pb->getRect().height()); + rdr::U16 y; const rdr::U16 w = pb->getRect().width(); const rdr::U16 h = pb->getRect().height(); @@ -873,8 +884,10 @@ void VNCServerST::writeUpdate() assert(blockCounter == 0); assert(desktopStarted); - if (DLPRegion.enabled) + if (DLPRegion.enabled) { + comparer->enable_copyrect(false); blackOut(); + } comparer->getUpdateInfo(&ui, pb->getRect()); toCheck = ui.changed.union_(ui.copied); diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index b42f63c..7872141 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -95,7 +95,7 @@ namespace rfb { virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout); virtual void setPixelBuffer(PixelBuffer* pb); virtual void setScreenLayout(const ScreenSet& layout); - virtual PixelBuffer* getPixelBuffer() const { return pb; } + virtual PixelBuffer* getPixelBuffer() const { if (DLPRegion.enabled && blackedpb) return blackedpb; else return pb; } virtual void serverCutText(const char* str, int len); virtual void add_changed(const Region ®ion); virtual void add_copied(const Region &dest, const Point &delta); @@ -209,6 +209,7 @@ namespace rfb { bool desktopStarted; int blockCounter; PixelBuffer* pb; + ManagedPixelBuffer *blackedpb; ScreenSet screenLayout; unsigned int ledState; @@ -264,6 +265,8 @@ namespace rfb { bool percents; rdr::U16 pcx1, pcy1, pcx2, pcy2; } DLPRegion; + + void translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, rdr::U16 &y2) const; }; }; diff --git a/unix/xserver/hw/vnc/Input.c b/unix/xserver/hw/vnc/Input.c index 2519ad3..f8ac3fd 100644 --- a/unix/xserver/hw/vnc/Input.c +++ b/unix/xserver/hw/vnc/Input.c @@ -155,7 +155,8 @@ static void enqueueEvents(DeviceIntPtr dev, int n) } #endif /* XORG < 111 */ -void vncPointerButtonAction(int buttonMask) +void vncPointerButtonAction(int buttonMask, const unsigned char skipclick, + const unsigned char skiprelease) { int i; #if XORG < 111 @@ -169,6 +170,14 @@ void vncPointerButtonAction(int buttonMask) if ((buttonMask ^ oldButtonMask) & (1 << i)) { int action = (buttonMask & (1< Date: Mon, 22 Mar 2021 11:23:49 +0000 Subject: [PATCH 08/12] Fixed syntax error --- common/rfb/VNCServerST.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 5cd317a..6c45ec3 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -830,6 +830,7 @@ void VNCServerST::translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, r x2 = pb->getRect().width() - 1; //slog.info("DLP_Region vals %u,%u %u,%u", x1, y1, x2, y2); +} void VNCServerST::blackOut() { From 93d3bf052d73deb90c611fbd12cce0cd3f0ff242 Mon Sep 17 00:00:00 2001 From: matt Date: Mon, 22 Mar 2021 11:36:33 +0000 Subject: [PATCH 09/12] Removed old line --- common/rfb/VNCServerST.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 6c45ec3..404b2eb 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -846,7 +846,6 @@ void VNCServerST::blackOut() int stride; const rdr::U8 *src = pb->getBuffer(pb->getRect(), &stride); rdr::U8 *data = blackedpb->getBufferRW(pb->getRect(), &stride); - rdr::U8 *data = mpb->getBufferRW(mpb->getRect(), &stride); stride *= 4; memcpy(data, src, stride * pb->getRect().height()); From 1632f4888d04c20d40f4a6cae0d9731650e9d6be Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 25 Mar 2021 11:25:30 +0200 Subject: [PATCH 10/12] Enable basicauth by default, remove the option to supply it on the command line --- common/network/TcpSocket.cxx | 14 +++++++------- common/network/TcpSocket.h | 6 +++--- common/network/websocket.c | 9 +++------ common/network/websocket.h | 2 +- common/rfb/Configuration.cxx | 3 +-- common/rfb/VNCSConnectionST.cxx | 9 ++++----- common/rfb/VNCServerST.cxx | 1 - unix/xserver/hw/vnc/Xvnc.man | 6 +++--- unix/xserver/hw/vnc/vncExtInit.cc | 4 ++-- 9 files changed, 24 insertions(+), 30 deletions(-) diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index c3733c3..4262be5 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -462,7 +462,7 @@ static uint8_t givecontrolCb(void *messager, const char name[]) WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir) { int one = 1; @@ -532,7 +532,7 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.passwdfile = strdup(wexp.we_wordv[0]); wordfree(&wexp); - settings.basicauth = basicauth; + settings.disablebasicauth = disablebasicauth; settings.cert = cert; settings.key = certkey; settings.ssl_only = sslonly; @@ -718,7 +718,7 @@ void network::createTcpListeners(std::list *listeners, void network::createWebsocketListeners(std::list *listeners, const struct addrinfo *ai, bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir) { const struct addrinfo *current; @@ -745,7 +745,7 @@ void network::createWebsocketListeners(std::list *listeners, try { new_listeners.push_back(new WebsocketListener(current->ai_addr, current->ai_addrlen, - sslonly, cert, certkey, basicauth, + sslonly, cert, certkey, disablebasicauth, httpdir)); } catch (SocketException& e) { // Ignore this if it is due to lack of address family support on @@ -774,7 +774,7 @@ void network::createWebsocketListeners(std::list *listeners, bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir) { if (addr && !strcmp(addr, "local")) { @@ -802,7 +802,7 @@ void network::createWebsocketListeners(std::list *listeners, ai[1].ai_addrlen = sizeof(sa[1].u.sin6); ai[1].ai_next = NULL; - createWebsocketListeners(listeners, ai, sslonly, cert, certkey, basicauth, httpdir); + createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir); } else { struct addrinfo *ai, hints; char service[16]; @@ -825,7 +825,7 @@ void network::createWebsocketListeners(std::list *listeners, gai_strerror(result)); try { - createWebsocketListeners(listeners, ai, sslonly, cert, certkey, basicauth, httpdir); + createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir); } catch(...) { freeaddrinfo(ai); throw; diff --git a/common/network/TcpSocket.h b/common/network/TcpSocket.h index dd98ce9..3e2dc1e 100644 --- a/common/network/TcpSocket.h +++ b/common/network/TcpSocket.h @@ -91,7 +91,7 @@ namespace network { public: WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir); virtual int getMyPort(); @@ -116,7 +116,7 @@ namespace network { bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir); void createTcpListeners(std::list *listeners, const char *addr, @@ -128,7 +128,7 @@ namespace network { bool sslonly, const char *cert, const char *certkey, - const char *basicauth, + bool disablebasicauth, const char *httpdir); typedef struct vnc_sockaddr { diff --git a/common/network/websocket.c b/common/network/websocket.c index a035426..f0d7f0b 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -1152,9 +1152,8 @@ ws_ctx_t *do_handshake(int sock) { usleep(10); } - const char *colon; unsigned char owner = 0; - if ((colon = strchr(settings.basicauth, ':'))) { + if (!settings.disablebasicauth) { const char *hdr = strstr(handshake, "Authorization: Basic "); if (!hdr) { handler_emsg("BasicAuth required, but client didn't send any. 401 Unauth\n"); @@ -1179,15 +1178,13 @@ ws_ctx_t *do_handshake(int sock) { tmp[len] = '\0'; len = ws_b64_pton(tmp, response, 256); - char authbuf[4096]; - strncpy(authbuf, settings.basicauth, 4096); - authbuf[4095] = '\0'; + char authbuf[4096] = ""; // Do we need to read it from the file? char *resppw = strchr(response, ':'); if (resppw && *resppw) resppw++; - if (!colon[1] && settings.passwdfile) { + if (settings.passwdfile) { if (resppw && *resppw && resppw - response < 32) { char pwbuf[4096]; struct kasmpasswd_t *set = readkasmpasswd(settings.passwdfile); diff --git a/common/network/websocket.h b/common/network/websocket.h index e96fc95..fd00987 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -71,7 +71,7 @@ typedef struct { unsigned int handler_id; const char *cert; const char *key; - const char *basicauth; + uint8_t disablebasicauth; const char *passwdfile; int ssl_only; const char *httpdir; diff --git a/common/rfb/Configuration.cxx b/common/rfb/Configuration.cxx index ff03a6f..fdfc4d5 100644 --- a/common/rfb/Configuration.cxx +++ b/common/rfb/Configuration.cxx @@ -433,8 +433,7 @@ bool StringParameter::setParam(const char* v) { if (immutable) return true; if (!v) throw rfb::Exception("setParam() not allowed"); - if (strcasecmp(getName(), "BasicAuth")) // don't log the auth info - vlog.debug("set %s(String) to %s", getName(), v); + vlog.debug("set %s(String) to %s", getName(), v); CharArray oldValue(value); value = strDup(v); return value != 0; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 05f9282..31bd16e 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -48,7 +48,7 @@ static LogWriter vlog("VNCSConnST"); static Cursor emptyCursor(0, 0, Point(0, 0), NULL); -extern rfb::StringParameter basicauth; +extern rfb::BoolParameter disablebasicauth; VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, bool reverse) @@ -1044,13 +1044,12 @@ bool VNCSConnectionST::isShiftPressed() bool VNCSConnectionST::getPerms(bool &write, bool &owner) const { bool found = false; - const char *colon = strchr(basicauth, ':'); - if (!colon || colon[1]) { - // We're running without basicauth, or with both user:pass on the command line + if (disablebasicauth) { + // We're running without basicauth write = true; return true; } - if (colon && !colon[1] && user[0]) { + if (user[0]) { struct kasmpasswd_t *set = readkasmpasswd(kasmpasswdpath); unsigned i; for (i = 0; i < set->num; i++) { diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 404b2eb..7bec158 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -81,7 +81,6 @@ EncCache VNCServerST::encCache; // static char kasmpasswdpath[4096]; -extern rfb::StringParameter basicauth; // -=- Constructors/Destructor diff --git a/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man index 891cc91..809fc1c 100644 --- a/unix/xserver/hw/vnc/Xvnc.man +++ b/unix/xserver/hw/vnc/Xvnc.man @@ -339,9 +339,9 @@ are in the same file, use \fB-cert\fP. Require SSL for websocket connections. Default off, non-SSL allowed. . .TP -.B \-basicAuth \fIuser:pass\fP -Username and password for websocket connections. Default empty, no authentication required. -If the password is empty, read it from the \fB-KasmPasswordFile\fP. +.B \-disableBasicAuth +Disable basic auth for websocket connections. Default enabled, details read from +the \fB-KasmPasswordFile\fP. . .TP .B \-SecurityTypes \fIsec-types\fP diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc index 057ceb5..99e00dc 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -89,7 +89,7 @@ rfb::IntParameter websocketPort("websocketPort", "websocket port to listen for", rfb::StringParameter cert("cert", "SSL pem cert to use for websocket connections", ""); rfb::StringParameter certkey("key", "SSL pem key to use for websocket connections (if separate)", ""); rfb::BoolParameter sslonly("sslOnly", "Require SSL for websockets", false); -rfb::StringParameter basicauth("BasicAuth", "user:pass for HTTP basic auth for websockets", ""); +rfb::BoolParameter disablebasicauth("DisableBasicAuth", "Disable basic auth for websockets", false); rfb::StringParameter interface("interface", "listen on the specified network address", "all"); @@ -225,7 +225,7 @@ void vncExtensionInit(void) if (!noWebsocket) network::createWebsocketListeners(&listeners, websocketPort, localhostOnly ? "local" : addr, - sslonly, cert, certkey, basicauth, httpDir); + sslonly, cert, certkey, disablebasicauth, httpDir); else if (localhostOnly) network::createLocalTcpListeners(&listeners, port); else From 4e8750e37a72ebfaac236055fc3f2adec3826ce5 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Thu, 25 Mar 2021 23:05:20 +1300 Subject: [PATCH 11/12] Disable passing of basicauth in vncserver --- unix/vncserver | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unix/vncserver b/unix/vncserver index 04c758e..68b4ad9 100644 --- a/unix/vncserver +++ b/unix/vncserver @@ -465,8 +465,7 @@ sub LoadConfig { } # change username option to basicAuth and add colon as required by Xvnc, password will be taken from file if ($k = "username") { - $config{"basicauth"} = "$v:"; - $vncUserName = $v; + next; } else { $config{$k} = $v; } From 9b3e1f73f5136c775ca8caa2cd28b318d5246ee1 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Fri, 26 Mar 2021 01:10:43 +1300 Subject: [PATCH 12/12] Deb/rpm: update barebones images --- builder/dockerfile.centos_core.barebones.rpm.test | 6 +++--- builder/dockerfile.fedora_thirtythree.barebones.rpm.test | 6 +++--- builder/dockerfile.ubuntu_focal.barebones.deb.test | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/builder/dockerfile.centos_core.barebones.rpm.test b/builder/dockerfile.centos_core.barebones.rpm.test index 33098d3..b5e22e9 100644 --- a/builder/dockerfile.centos_core.barebones.rpm.test +++ b/builder/dockerfile.centos_core.barebones.rpm.test @@ -2,6 +2,7 @@ FROM centos:centos7 RUN yum install -y xterm RUN yum install -y vim less +RUN yum install -y redhat-lsb-core ARG KASMVNC_PACKAGE_DIR COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp @@ -9,10 +10,9 @@ RUN yum localinstall -y /tmp/*.rpm RUN useradd -m foo -USER foo:kasmvnc +USER foo:kasmvnc-cert RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ chmod +x ~/.vnc/xstartup -RUN echo bar | kasmvncpasswd -f > $HOME/.kasmpasswd && chmod 0600 $HOME/.kasmpasswd -ENTRYPOINT bash -c "vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log " +ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log " diff --git a/builder/dockerfile.fedora_thirtythree.barebones.rpm.test b/builder/dockerfile.fedora_thirtythree.barebones.rpm.test index d9ab4d7..b592c89 100644 --- a/builder/dockerfile.fedora_thirtythree.barebones.rpm.test +++ b/builder/dockerfile.fedora_thirtythree.barebones.rpm.test @@ -2,6 +2,7 @@ FROM fedora:33 RUN dnf install -y xterm RUN dnf install -y vim less +RUN yum install -y redhat-lsb-core ARG KASMVNC_PACKAGE_DIR COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp @@ -9,10 +10,9 @@ RUN dnf localinstall -y /tmp/*.rpm RUN useradd -m foo -USER foo:kasmvnc +USER foo:kasmvnc-cert RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ chmod +x ~/.vnc/xstartup -RUN echo bar | kasmvncpasswd -f > $HOME/.kasmpasswd && chmod 0600 $HOME/.kasmpasswd -ENTRYPOINT bash -c "vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log " +ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log " diff --git a/builder/dockerfile.ubuntu_focal.barebones.deb.test b/builder/dockerfile.ubuntu_focal.barebones.deb.test index 4449d77..26b0c14 100644 --- a/builder/dockerfile.ubuntu_focal.barebones.deb.test +++ b/builder/dockerfile.ubuntu_focal.barebones.deb.test @@ -11,6 +11,5 @@ USER foo RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ chmod +x ~/.vnc/xstartup -RUN echo bar | kasmvncpasswd -f > $HOME/.kasmpasswd && chmod 0600 $HOME/.kasmpasswd -ENTRYPOINT bash -c "vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/ssl/certs/ssl-cert-snakeoil.pem -key /etc/ssl/private/ssl-cert-snakeoil.key -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log " +ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/ssl/certs/ssl-cert-snakeoil.pem -key /etc/ssl/private/ssl-cert-snakeoil.key -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "