From 896950ea13210916241dbae8079ed868dabd468c Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Fri, 9 Oct 2020 15:36:03 +0300 Subject: [PATCH 01/22] kasmvncpasswd: Add support for multiple users --- unix/kasmvncpasswd/CMakeLists.txt | 3 +- unix/kasmvncpasswd/kasmpasswd.c | 114 +++++++++++++++++++++++++++++ unix/kasmvncpasswd/kasmpasswd.h | 27 +++++++ unix/kasmvncpasswd/kasmvncpasswd.c | 112 ++++++++++++++++++---------- 4 files changed, 217 insertions(+), 39 deletions(-) create mode 100644 unix/kasmvncpasswd/kasmpasswd.c create mode 100644 unix/kasmvncpasswd/kasmpasswd.h diff --git a/unix/kasmvncpasswd/CMakeLists.txt b/unix/kasmvncpasswd/CMakeLists.txt index 2edb0d7..600ee34 100644 --- a/unix/kasmvncpasswd/CMakeLists.txt +++ b/unix/kasmvncpasswd/CMakeLists.txt @@ -1,7 +1,8 @@ include_directories(${CMAKE_SOURCE_DIR}/common) add_executable(kasmvncpasswd - kasmvncpasswd.c) + kasmvncpasswd.c + kasmpasswd.c) target_link_libraries(kasmvncpasswd crypt) diff --git a/unix/kasmvncpasswd/kasmpasswd.c b/unix/kasmvncpasswd/kasmpasswd.c new file mode 100644 index 0000000..03e6dd8 --- /dev/null +++ b/unix/kasmvncpasswd/kasmpasswd.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include + +#include "kasmpasswd.h" + +struct kasmpasswd_t *readkasmpasswd(const char path[]) { + + struct kasmpasswd_t *set = calloc(sizeof(struct kasmpasswd_t), 1); + FILE *f = fopen(path, "r"); + if (!f) + return set; + + // Count lines + unsigned lines = 0; + char buf[4096]; + + while (fgets(buf, 4096, f)) { + lines++; + } + + rewind(f); + + set->entries = calloc(sizeof(struct kasmpasswd_entry_t), lines); + + unsigned cur = 0; + while (fgets(buf, 4096, f)) { + char *lim = strchr(buf, ':'); + if (!lim) + continue; + *lim = '\0'; + lim++; + + const char * const pw = lim; + + lim = strchr(lim, ':'); + if (!lim) + continue; + *lim = '\0'; + lim++; + + const char * const perms = lim; + + lim = strchr(lim, '\n'); + if (lim) + *lim = '\0'; + + if (strlen(buf) + 1 > sizeof(((struct kasmpasswd_entry_t *)0)->user)) { + fprintf(stderr, "Username %s too long\n", buf); + continue; + } + if (strlen(pw) + 1 > sizeof(((struct kasmpasswd_entry_t *)0)->password)) { + fprintf(stderr, "Password for user %s too long\n", buf); + continue; + } + + strcpy(set->entries[cur].user, buf); + strcpy(set->entries[cur].password, pw); + + if (strchr(perms, 'w')) + set->entries[cur].write = 1; + if (strchr(perms, 'o')) + set->entries[cur].owner = 1; + + cur++; + } + + fclose(f); + + set->num = cur; + + return set; +} + +void writekasmpasswd(const char path[], const struct kasmpasswd_t *set) { + char tmpname[PATH_MAX]; + + if (!set || !set->entries || !set->num) + return; + + snprintf(tmpname, PATH_MAX, "%s.tmp", path); + tmpname[PATH_MAX - 1] = '\0'; + + FILE *f = fopen(tmpname, "w"); + if (!f) { + fprintf(stderr, "Failed to open temp file %s\n", tmpname); + return; + } + + static const char * const perms[] = { + "", + "w", + "o", + "ow" + }; + + unsigned i; + for (i = 0; i < set->num; i++) { + fprintf(f, "%s:%s:%s\n", + set->entries[i].user, + set->entries[i].password, + perms[set->entries[i].owner * 2 + set->entries[i].write]); + } + + fsync(fileno(f)); + fclose(f); + + if (rename(tmpname, path)) + fprintf(stderr, "Failed writing the password file %s\n", path); + chmod(path, S_IRUSR|S_IWUSR); +} diff --git a/unix/kasmvncpasswd/kasmpasswd.h b/unix/kasmvncpasswd/kasmpasswd.h new file mode 100644 index 0000000..6bff018 --- /dev/null +++ b/unix/kasmvncpasswd/kasmpasswd.h @@ -0,0 +1,27 @@ +#ifndef KASMPASSWD_H +#define KASMPASSWD_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct kasmpasswd_entry_t { + char user[32]; + char password[128]; + unsigned char write : 1; + unsigned char owner : 1; +}; + +struct kasmpasswd_t { + struct kasmpasswd_entry_t *entries; + unsigned num; +}; + +struct kasmpasswd_t *readkasmpasswd(const char path[]); +void writekasmpasswd(const char path[], const struct kasmpasswd_t *set); + +#ifdef __cplusplus +} // extern C +#endif + +#endif diff --git a/unix/kasmvncpasswd/kasmvncpasswd.c b/unix/kasmvncpasswd/kasmvncpasswd.c index 6c41da4..3e47375 100644 --- a/unix/kasmvncpasswd/kasmvncpasswd.c +++ b/unix/kasmvncpasswd/kasmvncpasswd.c @@ -29,10 +29,16 @@ #include #include +#include "kasmpasswd.h" + static void usage(const char *prog) { - fprintf(stderr, "Usage: %s [file]\n", prog); - fprintf(stderr, " %s -f\n", prog); + fprintf(stderr, "Usage: %s -u username [-wno] [file]\n" + "-w Write permission\n" + "-o Owner\n" + "-n Don't change password, change permissions only\n" + "\n" + "The file is updated atomically.\n", prog); exit(1); } @@ -68,18 +74,6 @@ static char* getpassword(const char* prompt, char *buf) { static char pw1[4096]; static char pw2[4096]; -// Reads passwords from stdin and prints encrypted passwords to stdout. -static int encrypt_pipe() { - char *result = getpassword(NULL, pw1); - if (!result) - return 1; - - printf("%s", encryptpw(result)); - fflush(stdout); - - return 0; -} - static const char *readpassword() { while (1) { if (!getpassword("Password:", pw1)) { @@ -110,20 +104,43 @@ static const char *readpassword() { int main(int argc, char** argv) { - char* fname = 0; + const char *fname = NULL; + const char *user = NULL; + const char args[] = "u:wno"; + int opt; - for (int i = 1; i < argc; i++) { - if (strncmp(argv[i], "-f", 2) == 0) { - return encrypt_pipe(); - } else if (argv[i][0] == '-') { - usage(argv[0]); - } else if (!fname) { - fname = argv[i]; - } else { - usage(argv[0]); + unsigned char nopass = 0, writer = 0, owner = 0; + + while ((opt = getopt(argc, argv, args)) != -1) { + switch (opt) { + case 'u': + user = optarg; + if (strlen(user) + 1 > sizeof(((struct kasmpasswd_entry_t *)0)->user)) { + fprintf(stderr, "Username %s too long\n", user); + exit(1); + } + break; + case 'n': + nopass = 1; + break; + case 'w': + writer = 1; + break; + case 'o': + owner = 1; + break; + default: + usage(argv[0]); + break; } } + if (!user) + usage(argv[0]); + + if (optind < argc) + fname = argv[optind]; + if (!fname) { wordexp_t wexp; if (!wordexp("~/.kasmpasswd", &wexp, WRDE_NOCMD) && wexp.we_wordv[0]) @@ -133,23 +150,42 @@ int main(int argc, char** argv) if (!fname) usage(argv[0]); - while (1) { + // Action + struct kasmpasswd_t *set = readkasmpasswd(fname); + unsigned i; + + if (nopass) { + for (i = 0; i < set->num; i++) { + if (!strcmp(set->entries[i].user, user)) { + set->entries[i].write = writer; + set->entries[i].owner = owner; + + writekasmpasswd(fname, set); + return 0; + } + } + fprintf(stderr, "No user named %s found\n", user); + return 1; + } else { const char *encrypted = readpassword(); - - FILE* fp = fopen(fname, "w"); - if (!fp) { - fprintf(stderr, "Couldn't open %s for writing\n", fname); - exit(1); - } - chmod(fname, S_IRUSR|S_IWUSR); - - if (fwrite(encrypted, strlen(encrypted), 1, fp) != 1) { - fprintf(stderr,"Writing to %s failed\n",fname); - exit(1); + for (i = 0; i < set->num; i++) { + if (!strcmp(set->entries[i].user, user)) + break; } - fclose(fp); + // No existing user by that name? + if (i >= set->num) { + i = set->num++; + set->entries = realloc(set->entries, set->num * sizeof(struct kasmpasswd_entry_t)); + } - return 0; + strcpy(set->entries[i].user, user); + strcpy(set->entries[i].password, encrypted); + set->entries[i].write = writer; + set->entries[i].owner = owner; + + writekasmpasswd(fname, set); } + + return 0; } From 30b9a82c08023b099383eb3549fcdd218d0eb5dc Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 12 Oct 2020 13:34:18 +0300 Subject: [PATCH 02/22] kasmvncpasswd: Add delete support --- unix/kasmvncpasswd/kasmpasswd.c | 3 +++ unix/kasmvncpasswd/kasmvncpasswd.c | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/unix/kasmvncpasswd/kasmpasswd.c b/unix/kasmvncpasswd/kasmpasswd.c index 03e6dd8..bee2870 100644 --- a/unix/kasmvncpasswd/kasmpasswd.c +++ b/unix/kasmvncpasswd/kasmpasswd.c @@ -99,6 +99,9 @@ void writekasmpasswd(const char path[], const struct kasmpasswd_t *set) { unsigned i; for (i = 0; i < set->num; i++) { + if (!set->entries[i].user[0]) + continue; + fprintf(f, "%s:%s:%s\n", set->entries[i].user, set->entries[i].password, diff --git a/unix/kasmvncpasswd/kasmvncpasswd.c b/unix/kasmvncpasswd/kasmvncpasswd.c index 3e47375..7ae5121 100644 --- a/unix/kasmvncpasswd/kasmvncpasswd.c +++ b/unix/kasmvncpasswd/kasmvncpasswd.c @@ -33,10 +33,11 @@ static void usage(const char *prog) { - fprintf(stderr, "Usage: %s -u username [-wno] [file]\n" + fprintf(stderr, "Usage: %s -u username [-wnod] [file]\n" "-w Write permission\n" "-o Owner\n" "-n Don't change password, change permissions only\n" + "-d Delete this user\n" "\n" "The file is updated atomically.\n", prog); exit(1); @@ -106,10 +107,10 @@ int main(int argc, char** argv) { const char *fname = NULL; const char *user = NULL; - const char args[] = "u:wno"; + const char args[] = "u:wnod"; int opt; - unsigned char nopass = 0, writer = 0, owner = 0; + unsigned char nopass = 0, writer = 0, owner = 0, deleting = 0; while ((opt = getopt(argc, argv, args)) != -1) { switch (opt) { @@ -129,12 +130,18 @@ int main(int argc, char** argv) case 'o': owner = 1; break; + case 'd': + deleting = 1; + break; default: usage(argv[0]); break; } } + if (deleting && (nopass || writer || owner)) + usage(argv[0]); + if (!user) usage(argv[0]); @@ -166,6 +173,18 @@ int main(int argc, char** argv) } fprintf(stderr, "No user named %s found\n", user); return 1; + + } else if (deleting) { + for (i = 0; i < set->num; i++) { + if (!strcmp(set->entries[i].user, user)) { + set->entries[i].user[0] = '\0'; + + writekasmpasswd(fname, set); + return 0; + } + } + fprintf(stderr, "No user named %s found\n", user); + return 1; } else { const char *encrypted = readpassword(); for (i = 0; i < set->num; i++) { From 9a5afc5a6275d0bc18738b374aa616eb7a4478ec Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 12 Oct 2020 13:47:16 +0300 Subject: [PATCH 03/22] Handle the new, multi-user kasmpasswd format auth --- common/network/CMakeLists.txt | 5 ++-- common/network/websocket.c | 54 +++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/common/network/CMakeLists.txt b/common/network/CMakeLists.txt index 485682b..51a32a9 100644 --- a/common/network/CMakeLists.txt +++ b/common/network/CMakeLists.txt @@ -1,10 +1,11 @@ -include_directories(${CMAKE_SOURCE_DIR}/common) +include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd) set(NETWORK_SOURCES Socket.cxx TcpSocket.cxx websocket.c - websockify.c) + websockify.c + ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd/kasmpasswd.c) if(NOT WIN32) set(NETWORK_SOURCES ${NETWORK_SOURCES} UnixSocket.cxx) diff --git a/common/network/websocket.c b/common/network/websocket.c index 3bc3b7f..1471210 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -29,6 +29,7 @@ #include /* md5 hash */ #include /* sha1 hash */ #include "websocket.h" +#include "kasmpasswd.h" /* * Global state @@ -913,31 +914,42 @@ ws_ctx_t *do_handshake(int sock) { if (resppw && *resppw) resppw++; if (!colon[1] && settings.passwdfile) { - if (resppw && *resppw) { + if (resppw && *resppw && resppw - response < 32) { char pwbuf[4096]; - FILE *f = fopen(settings.passwdfile, "r"); - if (f) { - handler_emsg("BasicAuth reading password from %s\n", settings.passwdfile); - const unsigned len = fread(pwbuf, 1, 4096, f); - fclose(f); - pwbuf[4095] = '\0'; - if (len < 4096) - pwbuf[len] = '\0'; - - snprintf(authbuf, 4096, "%s%s", settings.basicauth, pwbuf); - authbuf[4095] = '\0'; - - const char *encrypted = crypt(resppw, "$5$kasm$"); - *resppw = '\0'; - - snprintf(pwbuf, 4096, "%s%s", response, encrypted); - pwbuf[4095] = '\0'; - strcpy(response, pwbuf); - } else { - fprintf(stderr, " websocket %d: Error: BasicAuth configured to read password from file %s, but the file doesn't exist\n", + struct kasmpasswd_t *set = readkasmpasswd(settings.passwdfile); + if (!set->num) { + fprintf(stderr, " websocket %d: Error: BasicAuth configured to read password from file %s, but the file doesn't exist or has no valid users\n", wsthread_handler_id, settings.passwdfile); + } else { + unsigned i; + char inuser[32]; + unsigned char found = 0; + memcpy(inuser, response, resppw - response - 1); + inuser[resppw - response - 1] = '\0'; + + for (i = 0; i < set->num; i++) { + if (!strcmp(set->entries[i].user, inuser)) { + found = 1; // TODO write to wctx + snprintf(authbuf, 4096, "%s:%s", set->entries[i].user, + set->entries[i].password); + authbuf[4095] = '\0'; + break; + } + } + + if (!found) + handler_emsg("BasicAuth user %s not found\n", inuser); } + free(set->entries); + free(set); + + const char *encrypted = crypt(resppw, "$5$kasm$"); + *resppw = '\0'; + + snprintf(pwbuf, 4096, "%s%s", response, encrypted); + pwbuf[4095] = '\0'; + strcpy(response, pwbuf); } else { // Client tried an empty password, just fail them response[0] = '\0'; From 263d05a296295b2e36ceeca90b3d84999b6c2411 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 12 Oct 2020 14:45:31 +0300 Subject: [PATCH 04/22] Apply read-only perms upon connecting --- common/network/TcpSocket.cxx | 13 +++++++-- common/network/websocket.c | 14 ++++++---- common/network/websocket.h | 4 +++ common/network/websockify.c | 6 +++++ common/rfb/CMakeLists.txt | 3 ++- common/rfb/VNCSConnectionST.cxx | 47 +++++++++++++++++++++++++++++++++ common/rfb/VNCSConnectionST.h | 13 ++++++++- 7 files changed, 91 insertions(+), 9 deletions(-) diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index f5476e8..c557805 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -123,11 +123,20 @@ WebSocket::WebSocket(int sock) : Socket(sock) } char* WebSocket::getPeerAddress() { - return rfb::strDup("websocket"); + struct sockaddr_un addr; + socklen_t len = sizeof(struct sockaddr_un); + if (getpeername(getFd(), (struct sockaddr *) &addr, &len) != 0) { + vlog.error("unable to get peer name for socket"); + return rfb::strDup("websocket"); + } + return rfb::strDup(addr.sun_path + 1); } char* WebSocket::getPeerEndpoint() { - return rfb::strDup("websocket"); + char buf[1024]; + sprintf(buf, "%s::websocket", getPeerAddress()); + + return rfb::strDup(buf); } // -=- TcpSocket diff --git a/common/network/websocket.c b/common/network/websocket.c index 1471210..0278b76 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -107,7 +107,7 @@ ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len) { ws_ctx_t *alloc_ws_ctx() { ws_ctx_t *ctx; - if (! (ctx = malloc(sizeof(ws_ctx_t))) ) + if (! (ctx = calloc(sizeof(ws_ctx_t), 1)) ) { fatal("malloc()"); } if (! (ctx->cin_buf = malloc(BUFSIZE)) ) @@ -930,7 +930,8 @@ ws_ctx_t *do_handshake(int sock) { for (i = 0; i < set->num; i++) { if (!strcmp(set->entries[i].user, inuser)) { - found = 1; // TODO write to wctx + found = 1; + strcpy(ws_ctx->user, inuser); snprintf(authbuf, 4096, "%s:%s", set->entries[i].user, set->entries[i].password); authbuf[4095] = '\0'; @@ -1024,7 +1025,6 @@ void *subthread(void *ptr) { const int csock = pass->csock; wsthread_handler_id = pass->id; - free((void *) pass); ws_ctx_t *ws_ctx; @@ -1034,11 +1034,14 @@ void *subthread(void *ptr) { goto out; // Child process exits } + memcpy(ws_ctx->ip, pass->ip, sizeof(pass->ip)); + proxy_handler(ws_ctx); if (pipe_error) { handler_emsg("Closing due to SIGPIPE\n"); } out: + free((void *) pass); if (ws_ctx) { ws_socket_free(ws_ctx); @@ -1074,12 +1077,13 @@ void *start_server(void *unused) { error("ERROR on accept"); continue; } + struct wspass_t *pass = calloc(1, sizeof(struct wspass_t)); + inet_ntop(cli_addr.sin_family, &cli_addr.sin_addr, pass->ip, sizeof(pass->ip)); fprintf(stderr, " websocket %d: got client connection from %s\n", settings.handler_id, - inet_ntoa(cli_addr.sin_addr)); + pass->ip); pthread_t tid; - struct wspass_t *pass = calloc(1, sizeof(struct wspass_t)); pass->id = settings.handler_id; pass->csock = csock; pthread_create(&tid, NULL, subthread, pass); diff --git a/common/network/websocket.h b/common/network/websocket.h index 18a9b0d..3d757f1 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -53,11 +53,15 @@ typedef struct { char *cout_buf; char *tin_buf; char *tout_buf; + + char user[32]; + char ip[64]; } ws_ctx_t; struct wspass_t { int csock; unsigned id; + char ip[64]; }; typedef struct { diff --git a/common/network/websockify.c b/common/network/websockify.c index fe2f018..aaabb9a 100644 --- a/common/network/websockify.c +++ b/common/network/websockify.c @@ -227,7 +227,13 @@ void proxy_handler(ws_ctx_t *ws_ctx) { strcpy(addr.sun_path, ".KasmVNCSock"); addr.sun_path[0] = '\0'; + struct sockaddr_un myaddr; + myaddr.sun_family = AF_UNIX; + sprintf(myaddr.sun_path, ".%s@%s", ws_ctx->user, ws_ctx->ip); + myaddr.sun_path[0] = '\0'; + int tsock = socket(AF_UNIX, SOCK_STREAM, 0); + bind(tsock, (struct sockaddr *) &myaddr, sizeof(struct sockaddr_un)); handler_msg("connecting to VNC target\n"); diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt index 67adcf9..38d3472 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -1,4 +1,5 @@ -include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd) set(RFB_SOURCES Blacklist.cxx diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 18779a5..9b31d6c 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -38,6 +38,9 @@ #include #include #include +#include + +#include "kasmpasswd.h" using namespace rfb; @@ -45,6 +48,8 @@ static LogWriter vlog("VNCSConnST"); static Cursor emptyCursor(0, 0, Point(0, 0), NULL); +extern rfb::StringParameter basicauth; + VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, bool reverse) : sock(s), reverseConnection(reverse), @@ -65,6 +70,25 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, memset(bstats_total, 0, sizeof(bstats_total)); gettimeofday(&connStart, NULL); + // Check their permissions, if applicable + kasmpasswdpath[0] = '\0'; + wordexp_t wexp; + if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD)) + strncpy(kasmpasswdpath, wexp.we_wordv[0], 4096); + kasmpasswdpath[4095] = '\0'; + wordfree(&wexp); + + user[0] = '\0'; + const char *at = strchr(peerEndpoint.buf, '@'); + if (at && at - peerEndpoint.buf > 1 && at - peerEndpoint.buf < 32) { + memcpy(user, peerEndpoint.buf, at - peerEndpoint.buf); + user[at - peerEndpoint.buf] = '\0'; + } + + bool write, owner; + if (!getPerms(write, owner) || !write) + accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + // Configure the socket setSocketTimeouts(); lastEventTime = time(0); @@ -999,6 +1023,29 @@ bool VNCSConnectionST::isShiftPressed() return false; } +bool VNCSConnectionST::getPerms(bool &write, bool &owner) const +{ + bool found = false; + const char *colon = strchr(basicauth, ':'); + if (colon && !colon[1] && user[0]) { + struct kasmpasswd_t *set = readkasmpasswd(kasmpasswdpath); + unsigned i; + for (i = 0; i < set->num; i++) { + if (!strcmp(set->entries[i].user, user)) { + write = set->entries[i].write; + owner = set->entries[i].owner; + found = true; + break; + } + } + + free(set->entries); + free(set); + } + + return found; +} + void VNCSConnectionST::writeRTTPing() { char type; diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 76350ef..6c9f3ee 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -184,7 +184,13 @@ namespace rfb { // of a VNCSConnectioST to the server. These access rights are applied // such that the actual rights granted are the minimum of the server's // default access settings and the connection's access settings. - virtual void setAccessRights(AccessRights ar) {accessRights=ar;} + virtual void setAccessRights(AccessRights ar) { + accessRights = ar; + + bool write, owner; + if (!getPerms(write, owner) || !write) + accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + } // Timer callbacks virtual bool handleTimeout(Timer* t); @@ -193,6 +199,8 @@ namespace rfb { bool isShiftPressed(); + bool getPerms(bool &write, bool &owner) const; + // Congestion control void writeRTTPing(); bool isCongested(); @@ -249,6 +257,9 @@ namespace rfb { rdr::U64 bstats_total[BS_NUM]; struct timeval connStart; + char user[32]; + char kasmpasswdpath[4096]; + time_t lastEventTime; time_t pointerEventTime; Point pointerEventPos; From 0c83a86bc858a3bc9fbc5d56238436553162c7a0 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Tue, 13 Oct 2020 13:38:18 +0300 Subject: [PATCH 05/22] Dynamically apply permissions --- common/rfb/VNCSConnectionST.cxx | 18 ++++++++++- common/rfb/VNCSConnectionST.h | 6 ++++ common/rfb/VNCServerST.cxx | 54 +++++++++++++++++++++++++++++++++ common/rfb/VNCServerST.h | 2 ++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 9b31d6c..c8af424 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -59,7 +59,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, losslessTimer(this), kbdLogTimer(this), server(server_), updates(false), updateRenderedCursor(false), removeRenderedCursor(false), continuousUpdates(false), encodeManager(this, &server_->encCache), - pointerEventTime(0), + needsPermCheck(false), pointerEventTime(0), clientHasCursor(false), accessRights(AccessDefault), startTime(time(0)) { @@ -1126,6 +1126,22 @@ void VNCSConnectionST::writeFramebufferUpdate() if (isCongested()) return; + // Check for permission changes? + if (needsPermCheck) { + needsPermCheck = false; + + bool write, owner, ret; + ret = getPerms(write, owner); + if (!ret) { + close("User was deleted"); + return; + } else if (!write) { + accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + } else { + accessRights |= AccessPtrEvents | AccessKeyEvents; + } + } + // Updates often consists of many small writes, and in continuous // mode, we will also have small fence messages around the update. We // need to aggregate these in order to not clog up TCP's congestion diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 6c9f3ee..a0c1025 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -102,6 +102,10 @@ namespace rfb { // or because the current cursor position has not been set by this client. bool needRenderedCursor(); + void recheckPerms() { + needsPermCheck = true; + } + network::Socket* getSock() { return sock; } void add_changed(const Region& region) { updates.add_changed(region); } void add_changed_all() { updates.add_changed(server->pb->getRect()); } @@ -190,6 +194,7 @@ namespace rfb { bool write, owner; if (!getPerms(write, owner) || !write) accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + needsPermCheck = false; } // Timer callbacks @@ -259,6 +264,7 @@ namespace rfb { char user[32]; char kasmpasswdpath[4096]; + bool needsPermCheck; time_t lastEventTime; time_t pointerEventTime; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 0577ec0..1ac8597 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -63,6 +63,11 @@ #include +#include +#include +#include +#include + using namespace rfb; static LogWriter slog("VNCServerST"); @@ -73,6 +78,9 @@ EncCache VNCServerST::encCache; // -=- VNCServerST Implementation // +static char kasmpasswdpath[4096]; +extern rfb::StringParameter basicauth; + // -=- Constructors/Destructor VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) @@ -87,6 +95,26 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) { lastUserInputTime = lastDisconnectTime = time(0); slog.debug("creating single-threaded server %s", name.buf); + + kasmpasswdpath[0] = '\0'; + wordexp_t wexp; + if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD)) + strncpy(kasmpasswdpath, wexp.we_wordv[0], 4096); + kasmpasswdpath[4095] = '\0'; + wordfree(&wexp); + + if (kasmpasswdpath[0] && access(kasmpasswdpath, R_OK) == 0) { + // Set up a watch on the password file + inotifyfd = inotify_init(); + if (inotifyfd < 0) + slog.error("Failed to init inotify"); + + int flags = fcntl(inotifyfd, F_GETFL, 0); + fcntl(inotifyfd, F_SETFL, flags | O_NONBLOCK); + + if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0) + slog.error("Failed to set watch"); + } } VNCServerST::~VNCServerST() @@ -659,8 +687,34 @@ void VNCServerST::writeUpdate() encCache.clear(); encCache.enabled = clients.size() > 1; + // Check if the password file was updated + bool permcheck = false; + if (inotifyfd >= 0) { + char buf[256]; + int ret = read(inotifyfd, buf, 256); + int pos = 0; + while (ret > 0) { + const struct inotify_event * const ev = (struct inotify_event *) &buf[pos]; + + if (ev->mask & IN_IGNORED) { + // file was deleted, set new watch + if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0) + slog.error("Failed to set watch"); + } + + permcheck = true; + + ret -= sizeof(struct inotify_event) - ev->len; + pos += sizeof(struct inotify_event) - ev->len; + } + } + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; + + if (permcheck) + (*ci)->recheckPerms(); + (*ci)->add_copied(ui.copied, ui.copy_delta); (*ci)->add_copypassed(ui.copypassed); (*ci)->add_changed(ui.changed); diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 7eaf896..ef6e3e0 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -249,6 +249,8 @@ namespace rfb { bool disableclients; Timer frameTimer; + + int inotifyfd; }; }; From 36deba3a75df6c55ddcf07d3f681c425de557406 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 14 Oct 2020 14:27:08 +0300 Subject: [PATCH 06/22] Correct non-basicauth and command-line user:pass being read-only --- common/rfb/VNCSConnectionST.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index c8af424..e9964ee 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1027,6 +1027,11 @@ 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 + write = true; + return true; + } if (colon && !colon[1] && user[0]) { struct kasmpasswd_t *set = readkasmpasswd(kasmpasswdpath); unsigned i; From a27744bca61a07bf5d82f1d5703d067430d22818 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 14 Oct 2020 14:39:33 +0300 Subject: [PATCH 07/22] Enable TLS 1.1 and 1.2 --- common/network/websocket.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/network/websocket.c b/common/network/websocket.c index 0278b76..b52a040 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -161,12 +161,14 @@ ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, char * certfile, char * keyfi } - ctx->ssl_ctx = SSL_CTX_new(TLSv1_server_method()); + ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); if (ctx->ssl_ctx == NULL) { ERR_print_errors_fp(stderr); fatal("Failed to configure SSL context"); } + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, use_keyfile, SSL_FILETYPE_PEM) <= 0) { sprintf(msg, "Unable to load private key file %s\n", use_keyfile); From 1f69d1584a68032d73b8bd11c2bb9718d13de1ba Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 14 Oct 2020 15:44:48 +0300 Subject: [PATCH 08/22] Prevent read-only clients from changing kasm settings --- common/rfb/ConnParams.cxx | 11 +++++++---- common/rfb/ConnParams.h | 5 +++++ common/rfb/SMsgHandler.cxx | 1 + common/rfb/SMsgHandler.h | 2 ++ common/rfb/SMsgReader.cxx | 2 +- common/rfb/VNCSConnectionST.h | 4 ++++ 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 56f86c6..7942779 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include using namespace rfb; @@ -43,7 +44,7 @@ ConnParams::ConnParams() supportsContinuousUpdates(false), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), subsampling(subsampleUndefined), name_(0), verStrPos(0), - ledState_(ledUnknown) + ledState_(ledUnknown), shandler(NULL) { memset(kasmPassed, 0, KASM_NUM_SETTINGS); setName(""); @@ -124,6 +125,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) encodings_.clear(); encodings_.insert(encodingRaw); + bool canChangeSettings = !shandler || shandler->canChangeKasmSettings(); + for (int i = nEncodings-1; i >= 0; i--) { switch (encodings[i]) { case encodingCopyRect: @@ -184,11 +187,11 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) subsampling = subsample16X; break; case pseudoEncodingPreferBandwidth: - if (!rfb::Server::ignoreClientSettingsKasm) + if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings) Server::preferBandwidth.setParam(); break; case pseudoEncodingMaxVideoResolution: - if (!rfb::Server::ignoreClientSettingsKasm) + if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings) kasmPassed[KASM_MAX_VIDEO_RESOLUTION] = true; break; } @@ -205,7 +208,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) encodings[i] <= pseudoEncodingFineQualityLevel100) fineQualityLevel = encodings[i] - pseudoEncodingFineQualityLevel0; - if (!rfb::Server::ignoreClientSettingsKasm) { + if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings) { if (encodings[i] >= pseudoEncodingJpegVideoQualityLevel0 && encodings[i] <= pseudoEncodingJpegVideoQualityLevel9) Server::jpegVideoQuality.setParam(encodings[i] - pseudoEncodingJpegVideoQualityLevel0); diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index 448c410..7439943 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -42,6 +42,8 @@ namespace rfb { const int subsample8X = 4; const int subsample16X = 5; + class SMsgHandler; + class ConnParams { public: ConnParams(); @@ -74,6 +76,8 @@ namespace rfb { const PixelFormat& pf() const { return pf_; } void setPF(const PixelFormat& pf); + void setSHandler(SMsgHandler *s) { shandler = s; } + const char* name() const { return name_; } void setName(const char* name); @@ -136,6 +140,7 @@ namespace rfb { char verStr[13]; int verStrPos; unsigned int ledState_; + SMsgHandler *shandler; }; } #endif diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index c38458c..78ece78 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -24,6 +24,7 @@ using namespace rfb; SMsgHandler::SMsgHandler() { + cp.setSHandler(this); } SMsgHandler::~SMsgHandler() diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 6d5f5a1..a169621 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -56,6 +56,8 @@ namespace rfb { virtual void sendStats() = 0; + virtual bool canChangeKasmSettings() const = 0; + // InputHandler interface // The InputHandler methods will be called for the corresponding messages. diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 7550331..7fb3f2c 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -151,7 +151,7 @@ void SMsgReader::readSetMaxVideoResolution() width = is->readU16(); height = is->readU16(); - if (!rfb::Server::ignoreClientSettingsKasm) { + if (!rfb::Server::ignoreClientSettingsKasm && handler->canChangeKasmSettings()) { sprintf(tmp, "%ux%u", width, height); rfb::Server::maxVideoResolution.setParam(tmp); } diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index a0c1025..3eff9ad 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -183,6 +183,10 @@ namespace rfb { virtual void supportsLEDState(); virtual void sendStats(); + virtual bool canChangeKasmSettings() const { + return (accessRights & (AccessPtrEvents | AccessKeyEvents)) == + (AccessPtrEvents | AccessKeyEvents); + } // setAccessRights() allows a security package to limit the access rights // of a VNCSConnectioST to the server. These access rights are applied From 531705c05c57a0b3674d493c26a1819543209e8d Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Wed, 14 Oct 2020 15:50:28 +0300 Subject: [PATCH 09/22] Remove set-desktop-size perms from read-only clients --- common/rfb/VNCSConnectionST.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index e9964ee..c5f3956 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -87,7 +87,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, bool write, owner; if (!getPerms(write, owner) || !write) - accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents | AccessSetDesktopSize)); // Configure the socket setSocketTimeouts(); @@ -1141,9 +1141,9 @@ void VNCSConnectionST::writeFramebufferUpdate() close("User was deleted"); return; } else if (!write) { - accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)); + accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents | AccessSetDesktopSize)); } else { - accessRights |= AccessPtrEvents | AccessKeyEvents; + accessRights |= AccessPtrEvents | AccessKeyEvents | AccessSetDesktopSize; } } From 28d19cd5562090bd19522fb4984f53c87bac09bb Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 29 Oct 2020 19:24:59 +0000 Subject: [PATCH 10/22] Disable VNC password, fix bug when running inside Kasm VDI --- kasmweb/core/rfb.js | 13 ++----------- kasmweb/vnc.html | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/kasmweb/core/rfb.js b/kasmweb/core/rfb.js index 74c13ae..ba85123 100644 --- a/kasmweb/core/rfb.js +++ b/kasmweb/core/rfb.js @@ -1068,17 +1068,8 @@ export default class RFB extends EventTargetMixin { _negotiate_std_vnc_auth() { if (this._sock.rQwait("auth challenge", 16)) { return false; } - /* Empty passwords are allowed in VNC and since we use HTTPS basic auth, wich is superior, lets allow the bypass of the vnc password - if (!this._rfb_credentials.password) { - this.dispatchEvent(new CustomEvent( - "credentialsrequired", - { detail: { types: ["password"] } })); - return false; - } - */ - if (!this._rfb_credentials.password) { - this._rfb_credentials.password = ""; - } + // KasmVNC uses basic Auth, clear the VNC password, which is not used + this._rfb_credentials.password = ""; // TODO(directxman12): make genDES not require an Array const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); diff --git a/kasmweb/vnc.html b/kasmweb/vnc.html index 676c447..bce252b 100644 --- a/kasmweb/vnc.html +++ b/kasmweb/vnc.html @@ -71,9 +71,19 @@ loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js"; document.head.appendChild(loader); }); - window.addEventListener("load", function() { - document.getElementById("noVNC_connect_button").click(); - }); + + let isInsideKasmVDI = false; + try { + isInsideKasmVDI = (window.self !== window.top); + } catch (e) { + isInsideKasmVDI = true; + } + + if (!isInsideKasmVDI) { + window.addEventListener("load", function() { + document.getElementById("noVNC_connect_button").click(); + }); + } From c81df7f198ca53d71a26d6a330e98bc4d38c02cc Mon Sep 17 00:00:00 2001 From: matt Date: Sun, 7 Feb 2021 11:12:18 +0000 Subject: [PATCH 11/22] KASM-1298 manually merged in offline changes --- kasmweb/app/ui.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/kasmweb/app/ui.js b/kasmweb/app/ui.js index 952f109..a5b614a 100644 --- a/kasmweb/app/ui.js +++ b/kasmweb/app/ui.js @@ -416,6 +416,10 @@ const UI = { document.documentElement.classList.remove("noVNC_reconnecting"); const transition_elem = document.getElementById("noVNC_transition_text"); + if (WebUtil.isInsideKasmVDI()) + { + parent.postMessage({ action: 'connection_state', value: state}, '*' ); + } switch (state) { case 'init': break; @@ -1253,8 +1257,10 @@ const UI = { UI.rfb.addEventListener("credentialsrequired", UI.credentials); UI.rfb.addEventListener("securityfailure", UI.securityFailed); UI.rfb.addEventListener("capabilities", UI.updatePowerButton); - UI.rfb.addEventListener("clipboard", UI.clipboardReceive); - UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve); + if (UI.rfb.clipboardDown){ + UI.rfb.addEventListener("clipboard", UI.clipboardReceive); + } + UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve); document.addEventListener('mouseenter', UI.enterVNC); document.addEventListener('mouseleave', UI.leaveVNC); @@ -1459,7 +1465,9 @@ const UI = { if (event.data && event.data.action) { switch (event.data.action) { case 'clipboardsnd': - UI.rfb.clipboardPasteFrom(event.data.value); + if (UI.rfb.clipboardUp) { + UI.rfb.clipboardPasteFrom(event.data.value); + } break; case 'setvideoquality': UI.rfb.videoQuality = event.data.value; From e9626fdacfde24f86b492bea455084c333b1c64d Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Thu, 25 Feb 2021 23:13:32 +1300 Subject: [PATCH 12/22] VNC Docker startup: use new kasmvncpasswd with multiple users support --- builder/startup/vnc_startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/startup/vnc_startup.sh b/builder/startup/vnc_startup.sh index b2c3c59..7304a71 100755 --- a/builder/startup/vnc_startup.sh +++ b/builder/startup/vnc_startup.sh @@ -40,7 +40,7 @@ VNC_IP=$(hostname -i) # first entry is control, second is view (if only one is valid for both) mkdir -p "$HOME/.vnc" PASSWD_PATH="$HOME/.vnc/passwd" -echo "$VNC_PW" | kasmvncpasswd -f > $HOME/.kasmpasswd +kasmvncpasswd -w -u $VNC_USER $HOME/.kasmpasswd chmod 0600 $HOME/.kasmpasswd openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $HOME/.vnc/self.pem -out $HOME/.vnc/self.pem -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=kasm/emailAddress=none@none.none" From 78768d52ff7f628979223e04af6cb1fdb6d693e1 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Thu, 25 Feb 2021 23:14:50 +1300 Subject: [PATCH 13/22] VNC Docker startup: commented out verbose logging for ease of use --- builder/startup/vnc_startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/startup/vnc_startup.sh b/builder/startup/vnc_startup.sh index 7304a71..671bb06 100755 --- a/builder/startup/vnc_startup.sh +++ b/builder/startup/vnc_startup.sh @@ -71,7 +71,7 @@ detect_www_dir detect_cert_location echo -e "start vncserver with param: VNC_COL_DEPTH=$VNC_COL_DEPTH, VNC_RESOLUTION=$VNC_RESOLUTION\n..." -vncserver $DISPLAY -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $cert_option -sslOnly -interface 0.0.0.0 $VNCOPTIONS $package_www_dir_option #&> $STARTUPDIR/no_vnc_startup.log +vncserver $DISPLAY -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $cert_option -sslOnly -interface 0.0.0.0 $VNCOPTIONS $package_www_dir_option #-log *:stderr:100 #&> $STARTUPDIR/no_vnc_startup.log PID_SUN=$! From 59fe8ad9db25a57f27d418014153d839e72e4c0b Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 25 Feb 2021 13:15:02 +0200 Subject: [PATCH 14/22] kasmvncpasswd: Nicer behavior when piping a password, add example in help --- unix/kasmvncpasswd/kasmvncpasswd.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/unix/kasmvncpasswd/kasmvncpasswd.c b/unix/kasmvncpasswd/kasmvncpasswd.c index 7ae5121..cb893cc 100644 --- a/unix/kasmvncpasswd/kasmvncpasswd.c +++ b/unix/kasmvncpasswd/kasmvncpasswd.c @@ -39,13 +39,20 @@ static void usage(const char *prog) "-n Don't change password, change permissions only\n" "-d Delete this user\n" "\n" - "The file is updated atomically.\n", prog); + "The file is updated atomically.\n\n" + "To pass the password via a pipe, use\n" + "echo -e \"password\\npassword\\n\" | %s [-args]\n", + prog, prog); exit(1); } static void enableEcho(unsigned char enable) { struct termios attrs; + + if (!isatty(fileno(stdin))) + return; + tcgetattr(fileno(stdin), &attrs); if (enable) attrs.c_lflag |= ECHO; @@ -60,7 +67,7 @@ static const char *encryptpw(const char *in) { } static char* getpassword(const char* prompt, char *buf) { - if (prompt) fputs(prompt, stdout); + if (prompt && isatty(fileno(stdin))) fputs(prompt, stdout); enableEcho(0); char* result = fgets(buf, 4096, stdin); enableEcho(1); @@ -95,6 +102,10 @@ static const char *readpassword() { exit(1); } if (strcmp(pw1, pw2) != 0) { + if (!isatty(fileno(stdin))) { + fprintf(stderr,"Passwords don't match\n"); + exit(1); + } fprintf(stderr,"Passwords don't match - try again\n"); continue; } From f981b1e88a7bbf07af94f2d371459a3f46884a89 Mon Sep 17 00:00:00 2001 From: Kasm <44181855+kasmtech@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:10:00 -0500 Subject: [PATCH 15/22] Update ui.js --- kasmweb/app/ui.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/kasmweb/app/ui.js b/kasmweb/app/ui.js index a5b614a..1e2d7a6 100644 --- a/kasmweb/app/ui.js +++ b/kasmweb/app/ui.js @@ -172,7 +172,6 @@ const UI = { UI.initSetting('port', port); UI.initSetting('encrypt', (window.location.protocol === "https:")); UI.initSetting('view_clip', false); - UI.initSetting('resize', 'remote'); UI.initSetting('shared', true); UI.initSetting('view_only', false); UI.initSetting('show_dot', false); @@ -181,15 +180,26 @@ const UI = { UI.initSetting('reconnect', false); UI.initSetting('reconnect_delay', 5000); UI.initSetting('idle_disconnect', 20); - UI.initSetting('video_quality', 3); - UI.initSetting('clipboard_up', true); - UI.initSetting('clipboard_down', true); - UI.initSetting('clipboard_seamless', true); UI.initSetting('prefer_local_cursor', true); - UI.initSetting('enable_webp', true); UI.initSetting('toggle_control_panel', false); UI.initSetting('enable_perf_stats', false); - + + if (WebUtil.isInsideKasmVDI()) { + UI.initSetting('video_quality', 1); + UI.initSetting('clipboard_up', false); + UI.initSetting('clipboard_down', false); + UI.initSetting('clipboard_seamless', false); + UI.initSetting('enable_webp', false); + UI.initSetting('resize', 'off'); + } else { + UI.initSetting('video_quality', 3); + UI.initSetting('clipboard_up', true); + UI.initSetting('clipboard_down', true); + UI.initSetting('clipboard_seamless', true); + UI.initSetting('enable_webp', true); + UI.initSetting('resize', 'remote'); + } + UI.setupSettingLabels(); }, // Adds a link to the label elements on the corresponding input elements @@ -1257,9 +1267,7 @@ const UI = { UI.rfb.addEventListener("credentialsrequired", UI.credentials); UI.rfb.addEventListener("securityfailure", UI.securityFailed); UI.rfb.addEventListener("capabilities", UI.updatePowerButton); - if (UI.rfb.clipboardDown){ - UI.rfb.addEventListener("clipboard", UI.clipboardReceive); - } + UI.rfb.addEventListener("clipboard", UI.clipboardReceive); UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve); document.addEventListener('mouseenter', UI.enterVNC); @@ -1301,7 +1309,9 @@ const UI = { window.attachEvent('onload', WindowLoad); window.attachEvent('message', UI.receiveMessage); } - UI.rfb.addEventListener("clipboard", UI.clipboardRx); + if (UI.rfb.clipboardDown){ + UI.rfb.addEventListener("clipboard", UI.clipboardRx); + } UI.rfb.addEventListener("disconnect", UI.disconnectedRx); document.getElementById('noVNC_control_bar_anchor').setAttribute('style', 'display: none'); document.getElementById('noVNC_connect_dlg').innerHTML = ''; From 01d448f01822f09529866388b7f8ac2c85241fdc Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 19:46:18 +1300 Subject: [PATCH 16/22] Deb: run test image, instead of just printing command --- builder/test-deb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/test-deb b/builder/test-deb index dacd3d0..b419dbe 100755 --- a/builder/test-deb +++ b/builder/test-deb @@ -8,5 +8,5 @@ cd "$(dirname "$0")" docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \ -t kasmvnctester_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.deb.test . -echo docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \ +docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \ kasmvnctester_${os}:$os_codename From 7a1c1edeb45180fc4df5228285bc4a690aa76e34 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 20:48:17 +1300 Subject: [PATCH 17/22] Deb/rpm: change test user password to "foobar" kasmvncpasswd validates password, so "bar" would be too short. --- builder/test-deb | 2 +- builder/test-deb-barebones | 3 ++- builder/test-rpm | 2 +- builder/test-rpm-barebones | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/builder/test-deb b/builder/test-deb index b419dbe..86aa7c6 100755 --- a/builder/test-deb +++ b/builder/test-deb @@ -8,5 +8,5 @@ cd "$(dirname "$0")" docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \ -t kasmvnctester_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.deb.test . -docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \ +docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ kasmvnctester_${os}:$os_codename diff --git a/builder/test-deb-barebones b/builder/test-deb-barebones index 37f9e1b..cb94966 100755 --- a/builder/test-deb-barebones +++ b/builder/test-deb-barebones @@ -11,4 +11,5 @@ docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \ -f dockerfile.${os}_${os_codename}.barebones.deb.test . echo echo "You will be asked to set password. User name is docker." -docker run -it -p 443:8443 --rm kasmvnctester_barebones_${os}:$os_codename +docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ + kasmvnctester_barebones_${os}:$os_codename diff --git a/builder/test-rpm b/builder/test-rpm index b352962..7ecda73 100755 --- a/builder/test-rpm +++ b/builder/test-rpm @@ -10,5 +10,5 @@ docker build --build-arg \ KASMVNC_PACKAGE_DIR="build/${os}_${os_codename}" \ -t kasmvnctester_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.rpm.test . -echo docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \ +echo docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ kasmvnctester_${os}:$os_codename diff --git a/builder/test-rpm-barebones b/builder/test-rpm-barebones index 68ec718..7ad42cf 100755 --- a/builder/test-rpm-barebones +++ b/builder/test-rpm-barebones @@ -9,5 +9,5 @@ os_codename="${2:-core}" docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os}_${os_codename}" \ -t kasmvnctester_barebones_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.barebones.rpm.test . -docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \ +docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ kasmvnctester_barebones_${os}:$os_codename From e0c056f36b5e9ed8d4d43925d0b02c61f737533d Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 21:03:02 +1300 Subject: [PATCH 18/22] Add rw, ro and owner users on container startup --- builder/startup/vnc_startup.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/builder/startup/vnc_startup.sh b/builder/startup/vnc_startup.sh index 671bb06..c8972e5 100755 --- a/builder/startup/vnc_startup.sh +++ b/builder/startup/vnc_startup.sh @@ -34,13 +34,31 @@ detect_cert_location() { fi } +add_vnc_user() { + local username="$1" + local password="$2" + local permission_option="$3" + + echo "Adding user $username" + echo -e "$password\n$password" | kasmvncpasswd $permission_option \ + -u "$username" $HOME/.kasmpasswd +} + ## resolve_vnc_connection VNC_IP=$(hostname -i) # first entry is control, second is view (if only one is valid for both) mkdir -p "$HOME/.vnc" PASSWD_PATH="$HOME/.vnc/passwd" -kasmvncpasswd -w -u $VNC_USER $HOME/.kasmpasswd +# echo -e "$VNC_PW\n$VNC_PW" | kasmvncpasswd -w -u $VNC_USER $HOME/.kasmpasswd +add_vnc_user "$VNC_USER" "$VNC_PW" "-w" +add_vnc_user "$VNC_USER-ro" "$VNC_PW" +add_vnc_user "$VNC_USER-owner" "$VNC_PW" "-o" +add_vnc_user "$VNC_USER-to-delete" "$VNC_PW" + +kasmvncpasswd -n -u "$VNC_USER-owner" -w $HOME/.kasmpasswd +kasmvncpasswd -d -u "$VNC_USER-to-delete" $HOME/.kasmpasswd + chmod 0600 $HOME/.kasmpasswd openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $HOME/.vnc/self.pem -out $HOME/.vnc/self.pem -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=kasm/emailAddress=none@none.none" From 9fb709087d010b04e4d784be2f16969449fed279 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 21:21:49 +1300 Subject: [PATCH 19/22] Add verbose logging via KASMVNC_VERBOSE_LOGGING env var Running "KASMVNC_VERBOSE_LOGGING=1 builder/test-deb debian buster" will turn on verbose logging of kasmvncserver. --- builder/startup/vnc_startup.sh | 3 ++- builder/test-deb | 4 +++- builder/test-rpm | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/builder/startup/vnc_startup.sh b/builder/startup/vnc_startup.sh index c8972e5..93c6079 100755 --- a/builder/startup/vnc_startup.sh +++ b/builder/startup/vnc_startup.sh @@ -87,9 +87,10 @@ vncserver -kill $DISPLAY &> $HOME/.vnc/vnc_startup.log \ detect_www_dir detect_cert_location +[ -n "$KASMVNC_VERBOSE_LOGGING" ] && verbose_logging_option="-log *:stderr:100" echo -e "start vncserver with param: VNC_COL_DEPTH=$VNC_COL_DEPTH, VNC_RESOLUTION=$VNC_RESOLUTION\n..." -vncserver $DISPLAY -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $cert_option -sslOnly -interface 0.0.0.0 $VNCOPTIONS $package_www_dir_option #-log *:stderr:100 #&> $STARTUPDIR/no_vnc_startup.log +vncserver $DISPLAY -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $cert_option -sslOnly -interface 0.0.0.0 $VNCOPTIONS $package_www_dir_option $verbose_logging_option #&> $STARTUPDIR/no_vnc_startup.log PID_SUN=$! diff --git a/builder/test-deb b/builder/test-deb index 86aa7c6..9114a3e 100755 --- a/builder/test-deb +++ b/builder/test-deb @@ -8,5 +8,7 @@ cd "$(dirname "$0")" docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \ -t kasmvnctester_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.deb.test . -docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ +docker run -it -p 443:8443 --rm \ + -e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \ + -e "VNC_USER=foo" -e "VNC_PW=foobar" \ kasmvnctester_${os}:$os_codename diff --git a/builder/test-rpm b/builder/test-rpm index 7ecda73..719b65f 100755 --- a/builder/test-rpm +++ b/builder/test-rpm @@ -10,5 +10,8 @@ docker build --build-arg \ KASMVNC_PACKAGE_DIR="build/${os}_${os_codename}" \ -t kasmvnctester_${os}:$os_codename \ -f dockerfile.${os}_${os_codename}.rpm.test . -echo docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \ + +docker run -it -p 443:8443 --rm \ + -e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \ + -e "VNC_USER=foo" -e "VNC_PW=foobar" \ kasmvnctester_${os}:$os_codename From 5e5467a3b0fa81ad4d6a802160c30569c6a1a2f1 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 21:24:59 +1300 Subject: [PATCH 20/22] Add build-and-test-deb and -rpm It builds tarball, deb/rpm pckage, and runs the test image. It doesn't detect source changes, so it'll always build tarball and the package. --- build-and-test-deb | 16 ++++++++++++++++ build-and-test-rpm | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100755 build-and-test-deb create mode 100755 build-and-test-rpm diff --git a/build-and-test-deb b/build-and-test-deb new file mode 100755 index 0000000..3728692 --- /dev/null +++ b/build-and-test-deb @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +os="$1" +codename="$2" + +if [[ -z "$os" ]] || [[ -z "$codename" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +cd "$(dirname "$0")" + +./build-tarball "$os" "$codename" && ./build-deb "$os" "$codename" && \ + ./test-deb "$os" "$codename" diff --git a/build-and-test-rpm b/build-and-test-rpm new file mode 100755 index 0000000..e6d85b0 --- /dev/null +++ b/build-and-test-rpm @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +os="$1" +codename="$2" + +if [[ -z "$os" ]] || [[ -z "$codename" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +cd "$(dirname "$0")" + +./build-tarball "$os" "$codename" && ./build-rpm "$os" "$codename" && \ + ./test-rpm "$os" "$codename" From 6d0a020f4f8744b695266cb9d22ac1ac0dbd2f9e Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 21:28:14 +1300 Subject: [PATCH 21/22] Move build-and-test-* to builder/ --- build-and-test-deb => builder/build-and-test-deb | 0 build-and-test-rpm => builder/build-and-test-rpm | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename build-and-test-deb => builder/build-and-test-deb (100%) rename build-and-test-rpm => builder/build-and-test-rpm (100%) diff --git a/build-and-test-deb b/builder/build-and-test-deb similarity index 100% rename from build-and-test-deb rename to builder/build-and-test-deb diff --git a/build-and-test-rpm b/builder/build-and-test-rpm similarity index 100% rename from build-and-test-rpm rename to builder/build-and-test-rpm From 101695970bd605f1423a1a82f8e1cd5d5ccc2557 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sun, 28 Feb 2021 22:53:06 +1300 Subject: [PATCH 22/22] Reapply "Use crypt_r in threaded code" --- common/network/websocket.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/network/websocket.c b/common/network/websocket.c index 9e16e3c..21e22ee 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -948,7 +948,10 @@ ws_ctx_t *do_handshake(int sock) { free(set->entries); free(set); - const char *encrypted = crypt(resppw, "$5$kasm$"); + struct crypt_data cdata; + cdata.initialized = 0; + + const char *encrypted = crypt_r(resppw, "$5$kasm$", &cdata); *resppw = '\0'; snprintf(pwbuf, 4096, "%s%s", response, encrypted);