diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index d0ae84e..c3eae8e 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -19,6 +19,7 @@ #ifndef __NETWORK_GET_API_H__ #define __NETWORK_GET_API_H__ +#include #include #include #include @@ -29,7 +30,7 @@ namespace network { class GetAPIMessager { public: - GetAPIMessager(); + GetAPIMessager(const char *passwdfile_); // from main thread void mainUpdateScreen(rfb::PixelBuffer *pb); @@ -38,7 +39,27 @@ namespace network { 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 actionQueue; + private: + const char *passwdfile; + pthread_mutex_t screenMutex; rfb::ManagedPixelBuffer screenPb; uint16_t screenW, screenH; diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 6fe01a2..31eeb2b 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -53,10 +53,12 @@ static const struct TightJPEGConfiguration conf[10] = { { 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) { pthread_mutex_init(&screenMutex, NULL); + pthread_mutex_init(&userMutex, NULL); } // from main thread @@ -181,3 +183,102 @@ uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, 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; +} diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index db3aafa..c3733c3 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -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); } +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, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, @@ -524,8 +543,11 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.listen_sock = sock; - settings.messager = messager = new GetAPIMessager; + settings.messager = messager = new GetAPIMessager(settings.passwdfile); settings.screenshotCb = screenshotCb; + settings.adduserCb = adduserCb; + settings.removeCb = removeCb; + settings.givecontrolCb = givecontrolCb; pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); diff --git a/common/network/websocket.c b/common/network/websocket.c index 6759a09..422fd50 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -111,6 +111,29 @@ static const char *parse_get(const char * const in, const char * const opt, unsi 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 */ @@ -842,7 +865,7 @@ nope: } 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 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); 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)) @@ -935,6 +962,112 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { 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 diff --git a/common/network/websocket.h b/common/network/websocket.h index abe214b..e96fc95 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -80,6 +80,10 @@ typedef struct { 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; #ifdef __cplusplus diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index b999c51..a0b4998 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -641,6 +641,71 @@ int VNCServerST::msToNextUpdate() 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 // updates are pending and propagates them to the update tracker for // each client. It uses the ComparingUpdateTracker's compare() method @@ -711,9 +776,12 @@ void VNCServerST::writeUpdate() } } - if (apimessager) + if (apimessager) { apimessager->mainUpdateScreen(pb); + checkAPIMessages(apimessager); + } + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++;