Merge pull request #33 from kasmtech/vncserver-remove-basicauth

Vncserver remove basicauth
pull/34/head
Kasm 4 years ago committed by GitHub
commit 0638245ff9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,6 +2,7 @@ FROM centos:centos7
RUN yum install -y xterm RUN yum install -y xterm
RUN yum install -y vim less RUN yum install -y vim less
RUN yum install -y redhat-lsb-core
ARG KASMVNC_PACKAGE_DIR ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
@ -9,10 +10,9 @@ RUN yum localinstall -y /tmp/*.rpm
RUN useradd -m foo RUN useradd -m foo
USER foo:kasmvnc USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.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 "

@ -2,6 +2,7 @@ FROM fedora:33
RUN dnf install -y xterm RUN dnf install -y xterm
RUN dnf install -y vim less RUN dnf install -y vim less
RUN yum install -y redhat-lsb-core
ARG KASMVNC_PACKAGE_DIR ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
@ -9,10 +10,9 @@ RUN dnf localinstall -y /tmp/*.rpm
RUN useradd -m foo RUN useradd -m foo
USER foo:kasmvnc USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.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 "

@ -11,6 +11,5 @@ USER foo
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \ RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.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 "

@ -1,6 +1,7 @@
include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd) include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd)
set(NETWORK_SOURCES set(NETWORK_SOURCES
GetAPIMessager.cxx
Socket.cxx Socket.cxx
TcpSocket.cxx TcpSocket.cxx
websocket.c websocket.c

@ -0,0 +1,75 @@
/* 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 <kasmpasswd.h>
#include <pthread.h>
#include <rfb/PixelBuffer.h>
#include <rfb/PixelFormat.h>
#include <stdint.h>
#include <vector>
namespace network {
class GetAPIMessager {
public:
GetAPIMessager(const char *passwdfile_);
// 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);
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<action_data> actionQueue;
private:
const char *passwdfile;
pthread_mutex_t screenMutex;
rfb::ManagedPixelBuffer screenPb;
uint16_t screenW, screenH;
uint64_t screenHash;
std::vector<uint8_t> cachedJpeg;
uint16_t cachedW, cachedH;
uint8_t cachedQ;
};
}
#endif // __NETWORK_GET_API_H__

@ -0,0 +1,288 @@
/* 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 <inttypes.h>
#include <network/GetAPI.h>
#include <rfb/ConnParams.h>
#include <rfb/LogWriter.h>
#include <rfb/JpegCompressor.h>
#include <rfb/xxhash.h>
#include <stdio.h>
#include <stdlib.h>
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(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
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 (!screenW || !screenH)
vlog.error("Screenshot requested but no screenshot exists (screen hasn't been viewed)");
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;
}
#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;
}

@ -74,6 +74,8 @@ namespace network {
virtual ~ConnectionFilter() {} virtual ~ConnectionFilter() {}
}; };
class GetAPIMessager;
class SocketListener { class SocketListener {
public: public:
SocketListener(int fd); SocketListener(int fd);
@ -93,6 +95,8 @@ namespace network {
void setFilter(ConnectionFilter* f) {filter = f;} void setFilter(ConnectionFilter* f) {filter = f;}
int getFd() {return fd;} int getFd() {return fd;}
virtual GetAPIMessager *getMessager() { return NULL; }
protected: protected:
SocketListener(); SocketListener();

@ -42,6 +42,7 @@
#include <wordexp.h> #include <wordexp.h>
#include "websocket.h" #include "websocket.h"
#include <network/GetAPI.h>
#include <network/TcpSocket.h> #include <network/TcpSocket.h>
#include <rfb/LogWriter.h> #include <rfb/LogWriter.h>
#include <rfb/Configuration.h> #include <rfb/Configuration.h>
@ -431,10 +432,37 @@ int TcpListener::getMyPort() {
extern settings_t settings; 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);
}
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, WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
socklen_t listenaddrlen, socklen_t listenaddrlen,
bool sslonly, const char *cert, const char *certkey, bool sslonly, const char *cert, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir) const char *httpdir)
{ {
int one = 1; int one = 1;
@ -504,7 +532,7 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
settings.passwdfile = strdup(wexp.we_wordv[0]); settings.passwdfile = strdup(wexp.we_wordv[0]);
wordfree(&wexp); wordfree(&wexp);
settings.basicauth = basicauth; settings.disablebasicauth = disablebasicauth;
settings.cert = cert; settings.cert = cert;
settings.key = certkey; settings.key = certkey;
settings.ssl_only = sslonly; settings.ssl_only = sslonly;
@ -515,6 +543,12 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
settings.listen_sock = sock; settings.listen_sock = sock;
settings.messager = messager = new GetAPIMessager(settings.passwdfile);
settings.screenshotCb = screenshotCb;
settings.adduserCb = adduserCb;
settings.removeCb = removeCb;
settings.givecontrolCb = givecontrolCb;
pthread_t tid; pthread_t tid;
pthread_create(&tid, NULL, start_server, NULL); pthread_create(&tid, NULL, start_server, NULL);
} }
@ -684,7 +718,7 @@ void network::createTcpListeners(std::list<SocketListener*> *listeners,
void network::createWebsocketListeners(std::list<SocketListener*> *listeners, void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
const struct addrinfo *ai, const struct addrinfo *ai,
bool sslonly, const char *cert, const char *certkey, bool sslonly, const char *cert, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir) const char *httpdir)
{ {
const struct addrinfo *current; const struct addrinfo *current;
@ -711,7 +745,7 @@ void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
try { try {
new_listeners.push_back(new WebsocketListener(current->ai_addr, new_listeners.push_back(new WebsocketListener(current->ai_addr,
current->ai_addrlen, current->ai_addrlen,
sslonly, cert, certkey, basicauth, sslonly, cert, certkey, disablebasicauth,
httpdir)); httpdir));
} catch (SocketException& e) { } catch (SocketException& e) {
// Ignore this if it is due to lack of address family support on // Ignore this if it is due to lack of address family support on
@ -740,7 +774,7 @@ void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
bool sslonly, bool sslonly,
const char *cert, const char *cert,
const char *certkey, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir) const char *httpdir)
{ {
if (addr && !strcmp(addr, "local")) { if (addr && !strcmp(addr, "local")) {
@ -768,7 +802,7 @@ void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
ai[1].ai_addrlen = sizeof(sa[1].u.sin6); ai[1].ai_addrlen = sizeof(sa[1].u.sin6);
ai[1].ai_next = NULL; ai[1].ai_next = NULL;
createWebsocketListeners(listeners, ai, sslonly, cert, certkey, basicauth, httpdir); createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir);
} else { } else {
struct addrinfo *ai, hints; struct addrinfo *ai, hints;
char service[16]; char service[16];
@ -791,7 +825,7 @@ void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
gai_strerror(result)); gai_strerror(result));
try { try {
createWebsocketListeners(listeners, ai, sslonly, cert, certkey, basicauth, httpdir); createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir);
} catch(...) { } catch(...) {
freeaddrinfo(ai); freeaddrinfo(ai);
throw; throw;

@ -91,7 +91,7 @@ namespace network {
public: public:
WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen,
bool sslonly, const char *cert, const char *certkey, bool sslonly, const char *cert, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir); const char *httpdir);
virtual int getMyPort(); virtual int getMyPort();
@ -100,8 +100,12 @@ namespace network {
int internalSocket; int internalSocket;
virtual GetAPIMessager *getMessager() { return messager; }
protected: protected:
virtual Socket* createSocket(int fd); virtual Socket* createSocket(int fd);
private:
GetAPIMessager *messager;
}; };
void createLocalTcpListeners(std::list<SocketListener*> *listeners, void createLocalTcpListeners(std::list<SocketListener*> *listeners,
@ -112,7 +116,7 @@ namespace network {
bool sslonly, bool sslonly,
const char *cert, const char *cert,
const char *certkey, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir); const char *httpdir);
void createTcpListeners(std::list<SocketListener*> *listeners, void createTcpListeners(std::list<SocketListener*> *listeners,
const char *addr, const char *addr,
@ -124,7 +128,7 @@ namespace network {
bool sslonly, bool sslonly,
const char *cert, const char *cert,
const char *certkey, const char *certkey,
const char *basicauth, bool disablebasicauth,
const char *httpdir); const char *httpdir);
typedef struct vnc_sockaddr { typedef struct vnc_sockaddr {

@ -9,6 +9,7 @@
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
#include <ctype.h>
#include <pthread.h> #include <pthread.h>
#include <unistd.h> #include <unistd.h>
#include <crypt.h> #include <crypt.h>
@ -83,6 +84,55 @@ int resolve_host(struct in_addr *sin_addr, const char *hostname)
return 0; 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 "";
}
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 * SSL Wrapper Code
@ -814,6 +864,226 @@ nope:
ws_send(ws_ctx, buf, strlen(buf)); ws_send(ws_ctx, buf, strlen(buf));
} }
static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
char buf[4096], path[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';
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))
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;
}
} 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
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) { ws_ctx_t *do_handshake(int sock) {
char handshake[4096], response[4096], sha1[29], trailer[17]; char handshake[4096], response[4096], sha1[29], trailer[17];
char *scheme, *pre; char *scheme, *pre;
@ -882,8 +1152,8 @@ ws_ctx_t *do_handshake(int sock) {
usleep(10); usleep(10);
} }
const char *colon; unsigned char owner = 0;
if ((colon = strchr(settings.basicauth, ':'))) { if (!settings.disablebasicauth) {
const char *hdr = strstr(handshake, "Authorization: Basic "); const char *hdr = strstr(handshake, "Authorization: Basic ");
if (!hdr) { if (!hdr) {
handler_emsg("BasicAuth required, but client didn't send any. 401 Unauth\n"); handler_emsg("BasicAuth required, but client didn't send any. 401 Unauth\n");
@ -908,15 +1178,13 @@ ws_ctx_t *do_handshake(int sock) {
tmp[len] = '\0'; tmp[len] = '\0';
len = ws_b64_pton(tmp, response, 256); len = ws_b64_pton(tmp, response, 256);
char authbuf[4096]; char authbuf[4096] = "";
strncpy(authbuf, settings.basicauth, 4096);
authbuf[4095] = '\0';
// Do we need to read it from the file? // Do we need to read it from the file?
char *resppw = strchr(response, ':'); char *resppw = strchr(response, ':');
if (resppw && *resppw) if (resppw && *resppw)
resppw++; resppw++;
if (!colon[1] && settings.passwdfile) { if (settings.passwdfile) {
if (resppw && *resppw && resppw - response < 32) { if (resppw && *resppw && resppw - response < 32) {
char pwbuf[4096]; char pwbuf[4096];
struct kasmpasswd_t *set = readkasmpasswd(settings.passwdfile); struct kasmpasswd_t *set = readkasmpasswd(settings.passwdfile);
@ -938,6 +1206,9 @@ ws_ctx_t *do_handshake(int sock) {
snprintf(authbuf, 4096, "%s:%s", set->entries[i].user, snprintf(authbuf, 4096, "%s:%s", set->entries[i].user,
set->entries[i].password); set->entries[i].password);
authbuf[4095] = '\0'; authbuf[4095] = '\0';
if (set->entries[i].owner)
owner = 1;
break; break;
} }
} }
@ -978,9 +1249,28 @@ ws_ctx_t *do_handshake(int sock) {
if (!parse_handshake(ws_ctx, handshake)) { if (!parse_handshake(ws_ctx, handshake)) {
handler_emsg("Invalid WS request, maybe a HTTP one\n"); handler_emsg("Invalid WS request, maybe a HTTP one\n");
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]) if (settings.httpdir && settings.httpdir[0])
servefile(ws_ctx, handshake); servefile(ws_ctx, handshake);
done:
free_ws_ctx(ws_ctx); free_ws_ctx(ws_ctx);
return NULL; return NULL;
} }

@ -1,4 +1,5 @@
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <stdint.h>
#define BUFSIZE 65536 #define BUFSIZE 65536
#define DBUFSIZE (BUFSIZE * 3) / 4 - 20 #define DBUFSIZE (BUFSIZE * 3) / 4 - 20
@ -70,10 +71,19 @@ typedef struct {
unsigned int handler_id; unsigned int handler_id;
const char *cert; const char *cert;
const char *key; const char *key;
const char *basicauth; uint8_t disablebasicauth;
const char *passwdfile; const char *passwdfile;
int ssl_only; int ssl_only;
const char *httpdir; 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);
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; } settings_t;
#ifdef __cplusplus #ifdef __cplusplus

@ -433,8 +433,7 @@ bool StringParameter::setParam(const char* v) {
if (immutable) return true; if (immutable) return true;
if (!v) if (!v)
throw rfb::Exception("setParam(<null>) not allowed"); throw rfb::Exception("setParam(<null>) 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); CharArray oldValue(value);
value = strDup(v); value = strDup(v);
return value != 0; return value != 0;

@ -966,7 +966,7 @@ static PixelBuffer *bilinearScale(const PixelBuffer *pb, const uint16_t w, const
return newpb; return newpb;
} }
static PixelBuffer *progressiveBilinearScale(const PixelBuffer *pb, PixelBuffer *progressiveBilinearScale(const PixelBuffer *pb,
const uint16_t tgtw, const uint16_t tgth, const uint16_t tgtw, const uint16_t tgth,
const float tgtdiff) const float tgtdiff)
{ {

@ -36,7 +36,9 @@ namespace rfb {
rdr::U32 __unused_attr keycode, rdr::U32 __unused_attr keycode,
bool __unused_attr down) { } bool __unused_attr down) { }
virtual void pointerEvent(const Point& __unused_attr pos, 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, virtual void clientCutText(const char* __unused_attr str,
int __unused_attr len) { } int __unused_attr len) { }
}; };

@ -217,7 +217,7 @@ void SMsgReader::readPointerEvent()
int mask = is->readU8(); int mask = is->readU8();
int x = is->readU16(); int x = is->readU16();
int y = is->readU16(); int y = is->readU16();
handler->pointerEvent(Point(x, y), mask); handler->pointerEvent(Point(x, y), mask, false, false);
} }

@ -163,6 +163,19 @@ rfb::StringParameter rfb::Server::DLP_ClipLog
("DLP_Log", ("DLP_Log",
"Log clipboard/kbd actions. Accepts off, info or verbose", "Log clipboard/kbd actions. Accepts off, info or verbose",
"off"); "off");
rfb::StringParameter rfb::Server::DLP_Region
("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 rfb::StringParameter rfb::Server::maxVideoResolution
("MaxVideoResolution", ("MaxVideoResolution",

@ -49,6 +49,9 @@ namespace rfb {
static IntParameter DLP_ClipDelay; static IntParameter DLP_ClipDelay;
static IntParameter DLP_KeyRateLimit; static IntParameter DLP_KeyRateLimit;
static StringParameter DLP_ClipLog; static StringParameter DLP_ClipLog;
static StringParameter DLP_Region;
static BoolParameter DLP_RegionAllowClick;
static BoolParameter DLP_RegionAllowRelease;
static IntParameter jpegVideoQuality; static IntParameter jpegVideoQuality;
static IntParameter webpVideoQuality; static IntParameter webpVideoQuality;
static StringParameter maxVideoResolution; static StringParameter maxVideoResolution;

@ -48,7 +48,7 @@ static LogWriter vlog("VNCSConnST");
static Cursor emptyCursor(0, 0, Point(0, 0), NULL); static Cursor emptyCursor(0, 0, Point(0, 0), NULL);
extern rfb::StringParameter basicauth; extern rfb::BoolParameter disablebasicauth;
VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
bool reverse) bool reverse)
@ -637,7 +637,7 @@ void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
setCursor(); 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); pointerEventTime = lastEventTime = time(0);
server->lastUserInputTime = lastEventTime; server->lastUserInputTime = lastEventTime;
@ -649,7 +649,23 @@ void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
server->pointerClient = this; server->pointerClient = this;
else else
server->pointerClient = 0; 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);
} }
} }
@ -1028,13 +1044,12 @@ bool VNCSConnectionST::isShiftPressed()
bool VNCSConnectionST::getPerms(bool &write, bool &owner) const bool VNCSConnectionST::getPerms(bool &write, bool &owner) const
{ {
bool found = false; bool found = false;
const char *colon = strchr(basicauth, ':'); if (disablebasicauth) {
if (!colon || colon[1]) { // We're running without basicauth
// We're running without basicauth, or with both user:pass on the command line
write = true; write = true;
return true; return true;
} }
if (colon && !colon[1] && user[0]) { if (user[0]) {
struct kasmpasswd_t *set = readkasmpasswd(kasmpasswdpath); struct kasmpasswd_t *set = readkasmpasswd(kasmpasswdpath);
unsigned i; unsigned i;
for (i = 0; i < set->num; i++) { for (i = 0; i < set->num; i++) {

@ -168,7 +168,7 @@ namespace rfb {
virtual void queryConnection(const char* userName); virtual void queryConnection(const char* userName);
virtual void clientInit(bool shared); virtual void clientInit(bool shared);
virtual void setPixelFormat(const PixelFormat& pf); 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 keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len); virtual void clientCutText(const char* str, int len);
virtual void framebufferUpdateRequest(const Rect& r, bool incremental); virtual void framebufferUpdateRequest(const Rect& r, bool incremental);

@ -51,6 +51,8 @@
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <network/GetAPI.h>
#include <rfb/ComparingUpdateTracker.h> #include <rfb/ComparingUpdateTracker.h>
#include <rfb/KeyRemapper.h> #include <rfb/KeyRemapper.h>
#include <rfb/ListConnInfo.h> #include <rfb/ListConnInfo.h>
@ -79,23 +81,116 @@ EncCache VNCServerST::encCache;
// //
static char kasmpasswdpath[4096]; static char kasmpasswdpath[4096];
extern rfb::StringParameter basicauth;
// -=- Constructors/Destructor // -=- 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_) VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
: blHosts(&blacklist), desktop(desktop_), desktopStarted(false), : 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), name(strDup(name_)), pointerClient(0), comparer(0),
cursor(new Cursor(0, 0, Point(), NULL)), cursor(new Cursor(0, 0, Point(), NULL)),
renderedCursorInvalid(false), renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false), lastConnectionTime(0), disableclients(false),
frameTimer(this) frameTimer(this), apimessager(NULL)
{ {
lastUserInputTime = lastDisconnectTime = time(0); lastUserInputTime = lastDisconnectTime = time(0);
slog.debug("creating single-threaded server %s", name.buf); 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'; kasmpasswdpath[0] = '\0';
wordexp_t wexp; wordexp_t wexp;
if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD)) if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD))
@ -639,6 +734,138 @@ int VNCServerST::msToNextUpdate()
return frameTimer.getRemainingMs(); 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);
}
void VNCServerST::translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, rdr::U16 &y2) const
{
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);
}
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);
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();
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 // writeUpdate() is called on a regular interval in order to see what
// updates are pending and propagates them to the update tracker for // updates are pending and propagates them to the update tracker for
// each client. It uses the ComparingUpdateTracker's compare() method // each client. It uses the ComparingUpdateTracker's compare() method
@ -656,6 +883,11 @@ void VNCServerST::writeUpdate()
assert(blockCounter == 0); assert(blockCounter == 0);
assert(desktopStarted); assert(desktopStarted);
if (DLPRegion.enabled) {
comparer->enable_copyrect(false);
blackOut();
}
comparer->getUpdateInfo(&ui, pb->getRect()); comparer->getUpdateInfo(&ui, pb->getRect());
toCheck = ui.changed.union_(ui.copied); toCheck = ui.changed.union_(ui.copied);
@ -709,6 +941,12 @@ void VNCServerST::writeUpdate()
} }
} }
if (apimessager) {
apimessager->mainUpdateScreen(pb);
checkAPIMessages(apimessager);
}
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++; ci_next = ci; ci_next++;

@ -43,6 +43,7 @@ namespace rfb {
class ListConnInfo; class ListConnInfo;
class PixelBuffer; class PixelBuffer;
class KeyRemapper; class KeyRemapper;
class network::GetAPIMessager;
class VNCServerST : public VNCServer, class VNCServerST : public VNCServer,
public Timer::Callback, public Timer::Callback,
@ -94,7 +95,7 @@ namespace rfb {
virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout); virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout);
virtual void setPixelBuffer(PixelBuffer* pb); virtual void setPixelBuffer(PixelBuffer* pb);
virtual void setScreenLayout(const ScreenSet& layout); 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 serverCutText(const char* str, int len);
virtual void add_changed(const Region &region); virtual void add_changed(const Region &region);
virtual void add_copied(const Region &dest, const Point &delta); virtual void add_copied(const Region &dest, const Point &delta);
@ -186,6 +187,8 @@ namespace rfb {
bool getDisable() { return disableclients;}; bool getDisable() { return disableclients;};
void setDisable(bool disable) { disableclients = disable;}; void setDisable(bool disable) { disableclients = disable;};
void setAPIMessager(network::GetAPIMessager *msgr) { apimessager = msgr; }
protected: protected:
friend class VNCSConnectionST; friend class VNCSConnectionST;
@ -206,6 +209,7 @@ namespace rfb {
bool desktopStarted; bool desktopStarted;
int blockCounter; int blockCounter;
PixelBuffer* pb; PixelBuffer* pb;
ManagedPixelBuffer *blackedpb;
ScreenSet screenLayout; ScreenSet screenLayout;
unsigned int ledState; unsigned int ledState;
@ -232,6 +236,7 @@ namespace rfb {
void stopFrameClock(); void stopFrameClock();
int msToNextUpdate(); int msToNextUpdate();
void writeUpdate(); void writeUpdate();
void blackOut();
Region getPendingRegion(); Region getPendingRegion();
const RenderedCursor* getRenderedCursor(); const RenderedCursor* getRenderedCursor();
@ -251,6 +256,17 @@ namespace rfb {
Timer frameTimer; Timer frameTimer;
int inotifyfd; int inotifyfd;
network::GetAPIMessager *apimessager;
struct {
bool enabled;
int x1, y1, x2, y2;
bool percents;
rdr::U16 pcx1, pcy1, pcx2, pcy2;
} DLPRegion;
void translateDLPRegion(rdr::U16 &x1, rdr::U16 &y1, rdr::U16 &x2, rdr::U16 &y2) const;
}; };
}; };

@ -465,8 +465,7 @@ sub LoadConfig {
} }
# change username option to basicAuth and add colon as required by Xvnc, password will be taken from file # change username option to basicAuth and add colon as required by Xvnc, password will be taken from file
if ($k = "username") { if ($k = "username") {
$config{"basicauth"} = "$v:"; next;
$vncUserName = $v;
} else { } else {
$config{$k} = $v; $config{$k} = $v;
} }

@ -155,7 +155,8 @@ static void enqueueEvents(DeviceIntPtr dev, int n)
} }
#endif /* XORG < 111 */ #endif /* XORG < 111 */
void vncPointerButtonAction(int buttonMask) void vncPointerButtonAction(int buttonMask, const unsigned char skipclick,
const unsigned char skiprelease)
{ {
int i; int i;
#if XORG < 111 #if XORG < 111
@ -169,6 +170,14 @@ void vncPointerButtonAction(int buttonMask)
if ((buttonMask ^ oldButtonMask) & (1 << i)) { if ((buttonMask ^ oldButtonMask) & (1 << i)) {
int action = (buttonMask & (1<<i)) ? int action = (buttonMask & (1<<i)) ?
ButtonPress : ButtonRelease; ButtonPress : ButtonRelease;
if (action == ButtonPress && skipclick) {
buttonMask &= ~(1<<i);
continue;
} else if (action == ButtonRelease && skiprelease) {
buttonMask |= (1<<i);
continue;
}
#if XORG < 110 #if XORG < 110
n = GetPointerEvents(eventq, vncPointerDev, n = GetPointerEvents(eventq, vncPointerDev,
action, i + 1, action, i + 1,

@ -32,7 +32,8 @@ extern "C" {
void vncInitInputDevice(void); void vncInitInputDevice(void);
void vncPointerButtonAction(int buttonMask); void vncPointerButtonAction(int buttonMask, const unsigned char skipclick,
const unsigned char skiprelease);
void vncPointerMove(int x, int y); void vncPointerMove(int x, int y);
void vncGetPointerPos(int *x, int *y); void vncGetPointerPos(int *x, int *y);

@ -86,6 +86,8 @@ XserverDesktop::XserverDesktop(int screenIndex_,
i != listeners.end(); i != listeners.end();
i++) { i++) {
vncSetNotifyFd((*i)->getFd(), screenIndex, true, false); vncSetNotifyFd((*i)->getFd(), screenIndex, true, false);
if ((*i)->getMessager())
server->setAPIMessager((*i)->getMessager());
} }
} }
@ -416,11 +418,12 @@ void XserverDesktop::approveConnection(uint32_t opaqueId, bool accept,
// SDesktop callbacks // SDesktop callbacks
void XserverDesktop::pointerEvent(const Point& pos, int buttonMask) void XserverDesktop::pointerEvent(const Point& pos, int buttonMask,
const bool skipClick, const bool skipRelease)
{ {
vncPointerMove(pos.x + vncGetScreenX(screenIndex), vncPointerMove(pos.x + vncGetScreenX(screenIndex),
pos.y + vncGetScreenY(screenIndex)); pos.y + vncGetScreenY(screenIndex));
vncPointerButtonAction(buttonMask); vncPointerButtonAction(buttonMask, skipClick, skipRelease);
} }
void XserverDesktop::clientCutText(const char* str, int len) void XserverDesktop::clientCutText(const char* str, int len)

@ -86,7 +86,8 @@ public:
const char* rejectMsg=0); const char* rejectMsg=0);
// rfb::SDesktop callbacks // rfb::SDesktop callbacks
virtual void pointerEvent(const rfb::Point& pos, int buttonMask); virtual void pointerEvent(const rfb::Point& pos, int buttonMask,
const bool skipClick, const bool skipRelease);
virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len); virtual void clientCutText(const char* str, int len);
virtual unsigned int setScreenLayout(int fb_width, int fb_height, virtual unsigned int setScreenLayout(int fb_width, int fb_height,

@ -280,6 +280,20 @@ Kasm exposes a few settings to the client the standard VNC does not.
This param lets the server ignore those. This param lets the server ignore those.
. .
.TP .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_RegionAllowClick \fIbool\fP
Allow clicks inside the blacked-out region.
.
.TP
.B \-DLP_RegionAllowRelease \fIbool\fP
Allow click releases inside the blacked-out region.
.
.TP
.B \-DLP_ClipSendMax \fIbytes\fP .B \-DLP_ClipSendMax \fIbytes\fP
Limit clipboard bytes to send to clients in one transaction. Default 10,000. 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. 0 disables the limit, use \fBSendCutText\fP to disable clipboard sending entirely.
@ -325,9 +339,9 @@ are in the same file, use \fB-cert\fP.
Require SSL for websocket connections. Default off, non-SSL allowed. Require SSL for websocket connections. Default off, non-SSL allowed.
. .
.TP .TP
.B \-basicAuth \fIuser:pass\fP .B \-disableBasicAuth
Username and password for websocket connections. Default empty, no authentication required. Disable basic auth for websocket connections. Default enabled, details read from
If the password is empty, read it from the \fB-KasmPasswordFile\fP. the \fB-KasmPasswordFile\fP.
. .
.TP .TP
.B \-SecurityTypes \fIsec-types\fP .B \-SecurityTypes \fIsec-types\fP

@ -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 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::StringParameter certkey("key", "SSL pem key to use for websocket connections (if separate)", "");
rfb::BoolParameter sslonly("sslOnly", "Require SSL for websockets", false); 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", rfb::StringParameter interface("interface",
"listen on the specified network address", "listen on the specified network address",
"all"); "all");
@ -225,7 +225,7 @@ void vncExtensionInit(void)
if (!noWebsocket) if (!noWebsocket)
network::createWebsocketListeners(&listeners, websocketPort, network::createWebsocketListeners(&listeners, websocketPort,
localhostOnly ? "local" : addr, localhostOnly ? "local" : addr,
sslonly, cert, certkey, basicauth, httpDir); sslonly, cert, certkey, disablebasicauth, httpDir);
else if (localhostOnly) else if (localhostOnly)
network::createLocalTcpListeners(&listeners, port); network::createLocalTcpListeners(&listeners, port);
else else

Loading…
Cancel
Save