Add HTTP GET APIs for creating, removing users and giving control

pull/33/head
Lauri Kasanen 4 years ago
parent 3f6524ee30
commit 980eedd33b

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

Loading…
Cancel
Save