mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-12-27 00:58:54 +01:00
Merge pull request #26 from kasmtech/multiuser_and_new_ui
Packaging changes, multiuser passwd, clipboard (UI) changes
This commit is contained in:
commit
784a9c611d
16
builder/build-and-test-deb
Executable file
16
builder/build-and-test-deb
Executable 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
16
builder/build-and-test-rpm
Executable 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"
|
@ -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=$!
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -24,6 +24,7 @@ using namespace rfb;
|
||||
|
||||
SMsgHandler::SMsgHandler()
|
||||
{
|
||||
cp.setSHandler(this);
|
||||
}
|
||||
|
||||
SMsgHandler::~SMsgHandler()
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -249,6 +249,8 @@ namespace rfb {
|
||||
bool disableclients;
|
||||
|
||||
Timer frameTimer;
|
||||
|
||||
int inotifyfd;
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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]+)\./);
|
||||
|
@ -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>
|
||||
|
@ -1,7 +1,8 @@
|
||||
include_directories(${CMAKE_SOURCE_DIR}/common)
|
||||
|
||||
add_executable(kasmvncpasswd
|
||||
kasmvncpasswd.c)
|
||||
kasmvncpasswd.c
|
||||
kasmpasswd.c)
|
||||
|
||||
target_link_libraries(kasmvncpasswd crypt)
|
||||
|
||||
|
117
unix/kasmvncpasswd/kasmpasswd.c
Normal file
117
unix/kasmvncpasswd/kasmpasswd.c
Normal 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);
|
||||
}
|
27
unix/kasmvncpasswd/kasmpasswd.h
Normal file
27
unix/kasmvncpasswd/kasmpasswd.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user