Add HTTP GET APIs for creating, removing users and giving control
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
#ifndef __NETWORK_GET_API_H__
|
#ifndef __NETWORK_GET_API_H__
|
||||||
#define __NETWORK_GET_API_H__
|
#define __NETWORK_GET_API_H__
|
||||||
|
|
||||||
|
#include <kasmpasswd.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <rfb/PixelBuffer.h>
|
#include <rfb/PixelBuffer.h>
|
||||||
#include <rfb/PixelFormat.h>
|
#include <rfb/PixelFormat.h>
|
||||||
@@ -29,7 +30,7 @@ namespace network {
|
|||||||
|
|
||||||
class GetAPIMessager {
|
class GetAPIMessager {
|
||||||
public:
|
public:
|
||||||
GetAPIMessager();
|
GetAPIMessager(const char *passwdfile_);
|
||||||
|
|
||||||
// from main thread
|
// from main thread
|
||||||
void mainUpdateScreen(rfb::PixelBuffer *pb);
|
void mainUpdateScreen(rfb::PixelBuffer *pb);
|
||||||
@@ -38,7 +39,27 @@ namespace network {
|
|||||||
uint8_t *netGetScreenshot(uint16_t w, uint16_t h,
|
uint8_t *netGetScreenshot(uint16_t w, uint16_t h,
|
||||||
const uint8_t q, const bool dedup,
|
const uint8_t q, const bool dedup,
|
||||||
uint32_t &len, uint8_t *staging);
|
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:
|
private:
|
||||||
|
const char *passwdfile;
|
||||||
|
|
||||||
pthread_mutex_t screenMutex;
|
pthread_mutex_t screenMutex;
|
||||||
rfb::ManagedPixelBuffer screenPb;
|
rfb::ManagedPixelBuffer screenPb;
|
||||||
uint16_t screenW, screenH;
|
uint16_t screenW, screenH;
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ static const struct TightJPEGConfiguration conf[10] = {
|
|||||||
{ 100, subsampleNone } // 9
|
{ 100, subsampleNone } // 9
|
||||||
};
|
};
|
||||||
|
|
||||||
GetAPIMessager::GetAPIMessager(): screenW(0), screenH(0), screenHash(0),
|
GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_),
|
||||||
|
screenW(0), screenH(0), screenHash(0),
|
||||||
cachedW(0), cachedH(0), cachedQ(0) {
|
cachedW(0), cachedH(0), cachedQ(0) {
|
||||||
|
|
||||||
pthread_mutex_init(&screenMutex, NULL);
|
pthread_mutex_init(&screenMutex, NULL);
|
||||||
|
pthread_mutex_init(&userMutex, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// from main thread
|
// from main thread
|
||||||
@@ -181,3 +183,102 @@ uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h,
|
|||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -440,6 +440,25 @@ static uint8_t *screenshotCb(void *messager, uint16_t w, uint16_t h, const uint8
|
|||||||
return msgr->netGetScreenshot(w, h, q, dedup, *len, staging);
|
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,
|
||||||
@@ -524,8 +543,11 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
|
|||||||
|
|
||||||
settings.listen_sock = sock;
|
settings.listen_sock = sock;
|
||||||
|
|
||||||
settings.messager = messager = new GetAPIMessager;
|
settings.messager = messager = new GetAPIMessager(settings.passwdfile);
|
||||||
settings.screenshotCb = screenshotCb;
|
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);
|
||||||
|
|||||||
@@ -111,6 +111,29 @@ static const char *parse_get(const char * const in, const char * const opt, unsi
|
|||||||
return "";
|
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
|
||||||
*/
|
*/
|
||||||
@@ -842,7 +865,7 @@ nope:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
|
static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
|
||||||
char buf[4096], path[4096], fullpath[4096], args[4096] = "";
|
char buf[4096], path[4096], args[4096] = "";
|
||||||
uint8_t ret = 0; // 0 = continue checking
|
uint8_t ret = 0; // 0 = continue checking
|
||||||
|
|
||||||
if (strncmp(in, "GET ", 4)) {
|
if (strncmp(in, "GET ", 4)) {
|
||||||
@@ -871,7 +894,11 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
|
|||||||
memcpy(path, in, len);
|
memcpy(path, in, len);
|
||||||
path[len] = '\0';
|
path[len] = '\0';
|
||||||
|
|
||||||
wserr("Requested owner api '%s' with args '%s'\n", path, args);
|
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))
|
#define entry(x) if (!strcmp(path, x))
|
||||||
|
|
||||||
@@ -935,6 +962,112 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) {
|
|||||||
wserr("Invalid params to screenshot\n");
|
wserr("Invalid params to screenshot\n");
|
||||||
goto nope;
|
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
|
#undef entry
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ typedef struct {
|
|||||||
uint8_t *(*screenshotCb)(void *messager, uint16_t w, uint16_t h, const uint8_t q,
|
uint8_t *(*screenshotCb)(void *messager, uint16_t w, uint16_t h, const uint8_t q,
|
||||||
const uint8_t dedup,
|
const uint8_t dedup,
|
||||||
uint32_t *len, uint8_t *staging);
|
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
|
||||||
|
|||||||
@@ -641,6 +641,71 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -711,9 +776,12 @@ void VNCServerST::writeUpdate()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apimessager)
|
if (apimessager) {
|
||||||
apimessager->mainUpdateScreen(pb);
|
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++;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user