Merge pull request #26 from kasmtech/multiuser_and_new_ui

Packaging changes, multiuser passwd, clipboard (UI) changes
This commit is contained in:
Kasm 2021-03-01 10:46:20 -05:00 committed by GitHub
commit 784a9c611d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 621 additions and 139 deletions

16
builder/build-and-test-deb Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
set -e
os="$1"
codename="$2"
if [[ -z "$os" ]] || [[ -z "$codename" ]]; then
echo "Usage: $0 <os> <codename>" >&2
exit 1
fi
cd "$(dirname "$0")"
./build-tarball "$os" "$codename" && ./build-deb "$os" "$codename" && \
./test-deb "$os" "$codename"

16
builder/build-and-test-rpm Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
set -e
os="$1"
codename="$2"
if [[ -z "$os" ]] || [[ -z "$codename" ]]; then
echo "Usage: $0 <os> <codename>" >&2
exit 1
fi
cd "$(dirname "$0")"
./build-tarball "$os" "$codename" && ./build-rpm "$os" "$codename" && \
./test-rpm "$os" "$codename"

View File

@ -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"
echo "$VNC_PW" | kasmvncpasswd -f > $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"
@ -69,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 #&> $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=$!

View File

@ -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 .
echo docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=bar" \
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

View File

@ -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

View File

@ -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=bar" \
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

View File

@ -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

View File

@ -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)

View File

@ -124,11 +124,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

View File

@ -30,6 +30,7 @@
#include <openssl/md5.h> /* md5 hash */
#include <openssl/sha.h> /* sha1 hash */
#include "websocket.h"
#include "kasmpasswd.h"
/*
* Global state
@ -59,28 +60,28 @@ void fatal(char *msg)
exit(1);
}
/* resolve host with also IP address parsing */
int resolve_host(struct in_addr *sin_addr, const char *hostname)
{
if (!inet_aton(hostname, sin_addr)) {
struct addrinfo *ai, *cur;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
if (getaddrinfo(hostname, NULL, &hints, &ai))
return -1;
for (cur = ai; cur; cur = cur->ai_next) {
if (cur->ai_family == AF_INET) {
*sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
freeaddrinfo(ai);
return 0;
}
}
freeaddrinfo(ai);
return -1;
}
return 0;
}
/* resolve host with also IP address parsing */
int resolve_host(struct in_addr *sin_addr, const char *hostname)
{
if (!inet_aton(hostname, sin_addr)) {
struct addrinfo *ai, *cur;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
if (getaddrinfo(hostname, NULL, &hints, &ai))
return -1;
for (cur = ai; cur; cur = cur->ai_next) {
if (cur->ai_family == AF_INET) {
*sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
freeaddrinfo(ai);
return 0;
}
}
freeaddrinfo(ai);
return -1;
}
return 0;
}
/*
@ -107,7 +108,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)) )
@ -308,7 +309,7 @@ int decode_hixie(char *src, size_t srclength,
*left = srclength;
if (srclength == 2 &&
(src[0] == '\xff') &&
(src[0] == '\xff') &&
(src[1] == '\x00')) {
// client sent orderly close frame
*opcode = 0x8; // Close frame
@ -326,7 +327,7 @@ int decode_hixie(char *src, size_t srclength,
return len;
}
retlen += len;
start = end + 2; // Skip '\xff' end and '\x00' start
start = end + 2; // Skip '\xff' end and '\x00' start
framecount++;
} while (end < (src+srclength-1));
if (framecount > 1) {
@ -399,7 +400,7 @@ int decode_hybi(unsigned char *src, size_t srclength,
int i = 0, len, framecount = 0;
size_t remaining = 0;
unsigned int target_offset = 0, hdr_length = 0, payload_length = 0;
*left = srclength;
frame = src;
@ -499,7 +500,7 @@ int decode_hybi(unsigned char *src, size_t srclength,
snprintf(cntstr, 3, "%d", framecount);
traffic(cntstr);
}
*left = remaining;
return target_offset;
}
@ -543,7 +544,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
end = strstr(start, "\r\n");
strncpy(headers->origin, start, end-start);
headers->origin[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Version: ");
if (start) {
// HyBi/RFC 6455
@ -560,14 +561,14 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
end = strstr(start, "\r\n");
strncpy(headers->key1, start, end-start);
headers->key1[end-start] = '\0';
start = strstr(handshake, "\r\nConnection: ");
if (!start) { return 0; }
start += 14;
end = strstr(start, "\r\n");
strncpy(headers->connection, start, end-start);
headers->connection[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Protocol: ");
if (!start) { return 0; }
start += 26;
@ -592,7 +593,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
end = strstr(start, "\r\n");
strncpy(headers->key1, start, end-start);
headers->key1[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Key2: ");
if (!start) { return 0; }
start += 22;
@ -916,34 +917,46 @@ 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';
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);
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;
strcpy(ws_ctx->user, inuser);
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);
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);
pwbuf[4095] = '\0';
strcpy(response, pwbuf);
} else {
// Client tried an empty password, just fail them
response[0] = '\0';
@ -1001,7 +1014,7 @@ ws_ctx_t *do_handshake(int sock) {
snprintf(response, sizeof(response), SERVER_HANDSHAKE_HIXIE, pre, headers->origin,
pre, scheme, headers->host, headers->path, pre, "base64", trailer);
}
//handler_msg("response: %s\n", response);
ws_send(ws_ctx, response, strlen(response));
@ -1018,7 +1031,6 @@ void *subthread(void *ptr) {
const int csock = pass->csock;
wsthread_handler_id = pass->id;
free((void *) pass);
ws_ctx_t *ws_ctx;
@ -1028,11 +1040,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);
@ -1068,12 +1083,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);

View File

@ -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 {

View File

@ -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");

View File

@ -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

View File

@ -26,6 +26,7 @@
#include <rfb/ledStates.h>
#include <rfb/ConnParams.h>
#include <rfb/ServerCore.h>
#include <rfb/SMsgHandler.h>
#include <rfb/util.h>
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);

View File

@ -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

View File

@ -24,6 +24,7 @@ using namespace rfb;
SMsgHandler::SMsgHandler()
{
cp.setSHandler(this);
}
SMsgHandler::~SMsgHandler()

View File

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

View File

@ -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);
}

View File

@ -38,6 +38,9 @@
#include <ctype.h>
#include <stdlib.h>
#include <stdint.h>
#include <wordexp.h>
#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),
@ -54,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))
{
@ -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 | AccessSetDesktopSize));
// Configure the socket
setSocketTimeouts();
lastEventTime = time(0);
@ -1001,6 +1025,34 @@ 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]) {
// 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;
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;
@ -1081,6 +1133,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 | AccessSetDesktopSize));
} else {
accessRights |= AccessPtrEvents | AccessKeyEvents | AccessSetDesktopSize;
}
}
// 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

View File

@ -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()); }
@ -179,12 +183,23 @@ 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
// 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));
needsPermCheck = false;
}
// Timer callbacks
virtual bool handleTimeout(Timer* t);
@ -193,6 +208,8 @@ namespace rfb {
bool isShiftPressed();
bool getPerms(bool &write, bool &owner) const;
// Congestion control
void writeRTTPing();
bool isCongested();
@ -249,6 +266,10 @@ namespace rfb {
rdr::U64 bstats_total[BS_NUM];
struct timeval connStart;
char user[32];
char kasmpasswdpath[4096];
bool needsPermCheck;
time_t lastEventTime;
time_t pointerEventTime;
Point pointerEventPos;

View File

@ -63,6 +63,11 @@
#include <rdr/types.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <wordexp.h>
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);

View File

@ -249,6 +249,8 @@ namespace rfb {
bool disableclients;
Timer frameTimer;
int inotifyfd;
};
};

View File

@ -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
@ -416,6 +426,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;
@ -1254,7 +1268,7 @@ const UI = {
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);
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
document.addEventListener('mouseenter', UI.enterVNC);
document.addEventListener('mouseleave', UI.leaveVNC);
@ -1295,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 = '';
@ -1459,7 +1475,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;

View File

@ -1068,14 +1068,7 @@ 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;
}
*/
// KasmVNC uses basic Auth, clear the VNC password, which is not used
this._rfb_credentials.password = "";
// TODO(directxman12): make genDES not require an Array
@ -1353,7 +1346,7 @@ export default class RFB extends EventTargetMixin {
*/
if (!this.enableWebP)
return false;
// It's not possible to check for webp synchronously, and hacking promises
// It's not possible to check for webp synchronously, and hacking promises
// into everything would be too time-consuming. So test for FF and Chrome.
var uagent = navigator.userAgent.toLowerCase();
var match = uagent.match(/firefox\/([0-9]+)\./);

View File

@ -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();
});
}
</script>
<!-- actual script modules -->
<script type="module" crossorigin='use-credentials' src="app/ui.js"></script>

View File

@ -1,7 +1,8 @@
include_directories(${CMAKE_SOURCE_DIR}/common)
add_executable(kasmvncpasswd
kasmvncpasswd.c)
kasmvncpasswd.c
kasmpasswd.c)
target_link_libraries(kasmvncpasswd crypt)

View File

@ -0,0 +1,117 @@
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#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++) {
if (!set->entries[i].user[0])
continue;
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);
}

View File

@ -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

View File

@ -29,16 +29,30 @@
#include <unistd.h>
#include <wordexp.h>
#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 [-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\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;
@ -53,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);
@ -68,18 +82,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)) {
@ -100,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;
}
@ -110,20 +116,49 @@ 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:wnod";
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, deleting = 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;
case 'd':
deleting = 1;
break;
default:
usage(argv[0]);
break;
}
}
if (deleting && (nopass || writer || owner))
usage(argv[0]);
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 +168,54 @@ 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 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();
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;
}