Merge branch 'update_user' into 'master'

Update user

See merge request kasm-technologies/internal/KasmVNC!19
This commit is contained in:
Matthew McClaskey 2022-01-17 17:10:24 +00:00
commit 8422168154
10 changed files with 387 additions and 68 deletions

View File

@ -5,6 +5,7 @@ set(NETWORK_SOURCES
Blacklist.cxx
Socket.cxx
TcpSocket.cxx
jsonescape.c
websocket.c
websockify.c
${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd/kasmpasswd.c)

View File

@ -21,6 +21,7 @@
#include <kasmpasswd.h>
#include <pthread.h>
#include <network/GetAPIEnums.h>
#include <rfb/PixelBuffer.h>
#include <rfb/PixelFormat.h>
#include <stdint.h>
@ -54,15 +55,15 @@ namespace network {
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 netUpdateUser(const char name[], const uint64_t mask, const bool write,
const bool owner);
uint8_t netGiveControlTo(const char name[]);
void netGetUsers(const char **ptr);
void netGetBottleneckStats(char *buf, uint32_t len);
void netGetFrameStats(char *buf, uint32_t len);
uint8_t netServerFrameStatsReady();
enum USER_ACTION {
//USER_ADD, - handled locally for interactivity
USER_REMOVE,
USER_GIVE_CONTROL,
WANT_FRAME_STATS_SERVERONLY,
WANT_FRAME_STATS_ALL,
WANT_FRAME_STATS_OWNER,

View File

@ -0,0 +1,28 @@
/* 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_ENUMS_H__
#define __NETWORK_GET_API_ENUMS_H__
// Enums that need accessibility from both C and C++.
enum USER_UPDATE_MASK {
USER_UPDATE_WRITE_MASK = 1 << 0,
USER_UPDATE_OWNER_MASK = 1 << 1,
};
#endif

View File

@ -20,6 +20,7 @@
#include <inttypes.h>
#include <network/GetAPI.h>
#include <network/jsonescape.h>
#include <rfb/ConnParams.h>
#include <rfb/EncodeManager.h>
#include <rfb/LogWriter.h>
@ -282,8 +283,6 @@ uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const boo
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;
@ -299,8 +298,8 @@ uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const boo
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);
if (!strcmp(set->entries[s].user, name)) {
vlog.error("Can't create user %s, already exists", name);
goto out;
}
}
@ -311,10 +310,13 @@ uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const boo
set->entries[s] = act.data;
writekasmpasswd(passwdfile, set);
vlog.info("User %s created", act.data.user);
vlog.info("User %s created", name);
out:
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 1;
}
@ -324,19 +326,90 @@ uint8_t GetAPIMessager::netRemoveUser(const char name[]) {
return 0;
}
action_data act;
act.action = USER_REMOVE;
if (pthread_mutex_lock(&userMutex))
return 0;
memcpy(act.data.user, name, USERNAME_LEN);
act.data.user[USERNAME_LEN - 1] = '\0';
struct kasmpasswd_t *set = readkasmpasswd(passwdfile);
bool found = false;
unsigned s;
for (s = 0; s < set->num; s++) {
if (!strcmp(set->entries[s].user, name)) {
set->entries[s].user[0] = '\0';
found = true;
break;
}
}
if (found) {
writekasmpasswd(passwdfile, set);
vlog.info("User %s removed", name);
} else {
vlog.error("Tried to remove nonexistent user %s", name);
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 0;
}
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 1;
}
uint8_t GetAPIMessager::netUpdateUser(const char name[], const uint64_t mask,
const bool write, const bool owner) {
if (strlen(name) >= USERNAME_LEN) {
vlog.error("Username too long");
return 0;
}
if (!mask) {
vlog.error("Update_user without any updates?");
return 0;
}
if (pthread_mutex_lock(&userMutex))
return 0;
actionQueue.push_back(act);
struct kasmpasswd_t *set = readkasmpasswd(passwdfile);
bool found = false;
unsigned s;
for (s = 0; s < set->num; s++) {
if (!strcmp(set->entries[s].user, name)) {
if (mask & USER_UPDATE_WRITE_MASK)
set->entries[s].write = write;
if (mask & USER_UPDATE_OWNER_MASK)
set->entries[s].owner = owner;
found = true;
break;
}
}
if (found) {
writekasmpasswd(passwdfile, set);
vlog.info("User %s permissions updated", name);
} else {
vlog.error("Tried to update nonexistent user %s", name);
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 0;
}
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 1;
}
@ -346,22 +419,91 @@ uint8_t GetAPIMessager::netGiveControlTo(const char name[]) {
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);
struct kasmpasswd_t *set = readkasmpasswd(passwdfile);
bool found = false;
unsigned s;
for (s = 0; s < set->num; s++) {
if (!strcmp(set->entries[s].user, name)) {
set->entries[s].write = 1;
found = true;
} else {
set->entries[s].write = 0;
}
}
if (found) {
writekasmpasswd(passwdfile, set);
vlog.info("User %s given control", name);
} else {
vlog.error("Tried to give control to nonexistent user %s", name);
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 0;
}
pthread_mutex_unlock(&userMutex);
free(set->entries);
free(set);
return 1;
}
void GetAPIMessager::netGetUsers(const char **outptr) {
/*
[
{ "user": "username", "write": true, "owner": true },
{ "user": "username", "write": true, "owner": true }
]
*/
char *buf;
char escapeduser[USERNAME_LEN * 2];
if (pthread_mutex_lock(&userMutex)) {
*outptr = (char *) calloc(1, 1);
return;
}
struct kasmpasswd_t *set = readkasmpasswd(passwdfile);
buf = (char *) calloc(set->num, 80);
FILE *f = fmemopen(buf, set->num * 80, "w");
fprintf(f, "[\n");
unsigned s;
for (s = 0; s < set->num; s++) {
JSON_escape(set->entries[s].user, escapeduser);
fprintf(f, " { \"user\": \"%s\", \"write\": %s, \"owner\": %s }",
escapeduser,
set->entries[s].write ? "true" : "false",
set->entries[s].owner ? "true" : "false");
if (s == set->num - 1)
fprintf(f, "\n");
else
fprintf(f, ",\n");
}
free(set->entries);
free(set);
fprintf(f, "]\n");
fclose(f);
pthread_mutex_unlock(&userMutex);
*outptr = buf;
}
void GetAPIMessager::netGetBottleneckStats(char *buf, uint32_t len) {
/*
{

View File

@ -455,12 +455,25 @@ static uint8_t removeCb(void *messager, const char name[])
return msgr->netRemoveUser(name);
}
static uint8_t updateUserCb(void *messager, const char name[], const uint64_t mask,
const uint8_t write, const uint8_t owner)
{
GetAPIMessager *msgr = (GetAPIMessager *) messager;
return msgr->netUpdateUser(name, mask, write, owner);
}
static uint8_t givecontrolCb(void *messager, const char name[])
{
GetAPIMessager *msgr = (GetAPIMessager *) messager;
return msgr->netGiveControlTo(name);
}
static void getUsersCb(void *messager, const char **ptr)
{
GetAPIMessager *msgr = (GetAPIMessager *) messager;
msgr->netGetUsers(ptr);
}
static void bottleneckStatsCb(void *messager, char *buf, uint32_t len)
{
GetAPIMessager *msgr = (GetAPIMessager *) messager;
@ -613,7 +626,9 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
settings.screenshotCb = screenshotCb;
settings.adduserCb = adduserCb;
settings.removeCb = removeCb;
settings.updateUserCb = updateUserCb;
settings.givecontrolCb = givecontrolCb;
settings.getUsersCb = getUsersCb;
settings.bottleneckStatsCb = bottleneckStatsCb;
settings.frameStatsCb = frameStatsCb;

View File

@ -0,0 +1,81 @@
/* Copyright (C) 2022 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.
*/
#include "jsonescape.h"
void JSON_escape(const char *in, char *out) {
for (; *in; in++) {
if (in[0] == '\b') {
*out++ = '\\';
*out++ = 'b';
} else if (in[0] == '\f') {
*out++ = '\\';
*out++ = 'f';
} else if (in[0] == '\n') {
*out++ = '\\';
*out++ = 'n';
} else if (in[0] == '\r') {
*out++ = '\\';
*out++ = 'r';
} else if (in[0] == '\t') {
*out++ = '\\';
*out++ = 't';
} else if (in[0] == '"') {
*out++ = '\\';
*out++ = '"';
} else if (in[0] == '\\') {
*out++ = '\\';
*out++ = '\\';
} else {
*out++ = *in;
}
}
*out = '\0';
}
void JSON_unescape(const char *in, char *out) {
for (; *in; in++) {
if (in[0] == '\\' && in[1] == 'b') {
*out++ = '\b';
in++;
} else if (in[0] == '\\' && in[1] == 'f') {
*out++ = '\f';
in++;
} else if (in[0] == '\\' && in[1] == 'n') {
*out++ = '\n';
in++;
} else if (in[0] == '\\' && in[1] == 'r') {
*out++ = '\r';
in++;
} else if (in[0] == '\\' && in[1] == 't') {
*out++ = '\t';
in++;
} else if (in[0] == '\\' && in[1] == '"') {
*out++ = '"';
in++;
} else if (in[0] == '\\' && in[1] == '\\') {
*out++ = '\\';
in++;
} else {
*out++ = *in;
}
}
*out = '\0';
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2022 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_JSON_ESCAPE_H__
#define __NETWORK_JSON_ESCAPE_H__
#ifdef __cplusplus
extern "C" {
#endif
void JSON_escape(const char *in, char *out);
void JSON_unescape(const char *in, char *out);
#ifdef __cplusplus
} // extern C
#endif
#endif

View File

@ -1074,7 +1074,51 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
"200 OK");
ws_send(ws_ctx, buf, strlen(buf));
wserr("Passed remove_user request to main thread\n");
ret = 1;
} else entry("/api/update_user") {
char decname[1024] = "";
param = parse_get(args, "name", &len);
if (len) {
memcpy(buf, param, len);
buf[len] = '\0';
percent_decode(buf, decname, 0);
}
if (!decname[0])
goto nope;
uint64_t mask = 0;
uint8_t mywrite = 0;
param = parse_get(args, "write", &len);
if (len && isalpha(param[0])) {
mask |= USER_UPDATE_WRITE_MASK;
if (!strncmp(param, "true", len))
mywrite = 1;
}
uint8_t myowner = 0;
param = parse_get(args, "owner", &len);
if (len && isalpha(param[0])) {
mask |= USER_UPDATE_OWNER_MASK;
if (!strncmp(param, "true", len))
myowner = 1;
}
if (!settings.updateUserCb(settings.messager, decname, mask, mywrite, myowner)) {
wserr("Invalid params to update_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/give_control") {
char decname[1024] = "";
@ -1103,7 +1147,6 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
"200 OK");
ws_send(ws_ctx, buf, strlen(buf));
wserr("Passed give_control request to main thread\n");
ret = 1;
} else entry("/api/get_bottleneck_stats") {
char statbuf[4096];
@ -1120,6 +1163,23 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
wserr("Sent bottleneck stats to API caller\n");
ret = 1;
} else entry("/api/get_users") {
const char *ptr;
settings.getUsersCb(settings.messager, &ptr);
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: %lu\r\n"
"\r\n", strlen(ptr));
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, ptr, strlen(ptr));
free((char *) ptr);
wserr("Sent user list to API caller\n");
ret = 1;
} else entry("/api/get_frame_stats") {
char statbuf[4096], decname[1024];
unsigned waitfor;

View File

@ -1,5 +1,6 @@
#include <openssl/ssl.h>
#include <stdint.h>
#include "GetAPIEnums.h"
#define BUFSIZE 65536
#define DBUFSIZE (BUFSIZE * 3) / 4 - 20
@ -83,6 +84,8 @@ typedef struct {
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 (*updateUserCb)(void *messager, const char name[], const uint64_t mask,
const uint8_t write, const uint8_t owner);
uint8_t (*givecontrolCb)(void *messager, const char name[]);
void (*bottleneckStatsCb)(void *messager, char *buf, uint32_t len);
void (*frameStatsCb)(void *messager, char *buf, uint32_t len);
@ -94,6 +97,7 @@ typedef struct {
uint8_t (*ownerConnectedCb)(void *messager);
uint8_t (*numActiveUsersCb)(void *messager);
void (*getUsersCb)(void *messager, const char **buf);
uint8_t (*getClientFrameStatsNumCb)(void *messager);
uint8_t (*serverFrameStatsReadyCb)(void *messager);
} settings_t;

View File

@ -805,49 +805,8 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager,
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;
case network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY:
trackingFrameStats = act.action;
break;
@ -862,11 +821,6 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager,
memcpy(trackingClient, act.data.password, 128);
break;
}
if (set) {
free(set->entries);
free(set);
}
}
apimessager->actionQueue.clear();