/* * Copyright (c) 2019 - 2024 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "rfb-proto.h" #include "vec.h" #include "type-macros.h" #include "fb.h" #include "desktop-layout.h" #include "display.h" #include "neatvnc.h" #include "common.h" #include "pixels.h" #include "stream.h" #include "config.h" #include "usdt.h" #include "encoder.h" #include "enc-util.h" #include "cursor.h" #include "logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_TLS #include #endif #ifdef HAVE_CRYPTO #include "crypto.h" #endif #ifndef DRM_FORMAT_INVALID #define DRM_FORMAT_INVALID 0 #endif #ifndef DRM_FORMAT_MOD_LINEAR #define DRM_FORMAT_MOD_LINEAR DRM_FORMAT_MOD_NONE #endif #define DEFAULT_NAME "Neat VNC" #define SECURITY_TYPES_MAX 3 #define APPLE_DH_SERVER_KEY_LENGTH 256 #define UDIV_UP(a, b) (((a) + (b) - 1) / (b)) #define EXPORT __attribute__((visibility("default"))) static int send_desktop_resize(struct nvnc_client* client, struct nvnc_fb* fb); static bool send_ext_support_frame(struct nvnc_client* client); static enum rfb_encodings choose_frame_encoding(struct nvnc_client* client, const struct nvnc_fb*); static void on_encode_frame_done(struct encoder*, struct rcbuf*, uint64_t pts); static bool client_has_encoding(const struct nvnc_client* client, enum rfb_encodings encoding); static void process_fb_update_requests(struct nvnc_client* client); static void sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr); static const char* encoding_to_string(enum rfb_encodings encoding); static bool client_send_led_state(struct nvnc_client* client); #if defined(PROJECT_VERSION) EXPORT const char nvnc_version[] = PROJECT_VERSION; #else EXPORT const char nvnc_version[] = "UNKNOWN"; #endif extern const unsigned short code_map_qnum_to_linux[]; static uint64_t nvnc__htonll(uint64_t x) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return __builtin_bswap64(x); #else return x; #endif } static uint64_t gettime_us(clockid_t clock) { struct timespec ts = { 0 }; clock_gettime(clock, &ts); return ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL; } static void client_close(struct nvnc_client* client) { nvnc_log(NVNC_LOG_INFO, "Closing client connection %p: ref %d", client, client->ref); nvnc_cleanup_fn cleanup = client->common.cleanup_fn; if (cleanup) cleanup(client->common.userdata); nvnc_client_fn fn = client->cleanup_fn; if (fn) fn(client); if (client->current_fb) { nvnc_fb_release(client->current_fb); nvnc_fb_unref(client->current_fb); } #ifdef HAVE_CRYPTO crypto_key_del(client->apple_dh_secret); crypto_rsa_pub_key_del(client->rsa.pub); #endif LIST_REMOVE(client, link); stream_destroy(client->net_stream); if (client->encoder) { client->server->n_damage_clients -= !(client->encoder->impl->flags & ENCODER_IMPL_FLAG_IGNORES_DAMAGE); client->encoder->on_done = NULL; } encoder_unref(client->encoder); encoder_unref(client->zrle_encoder); encoder_unref(client->tight_encoder); pixman_region_fini(&client->damage); free(client->cut_text.buffer); free(client); } static inline void client_unref(struct nvnc_client* client) { assert(client->ref > 0); if (--client->ref == 0) client_close(client); } static inline void client_ref(struct nvnc_client* client) { ++client->ref; } static void do_deferred_client_close(void *obj) { client_unref(obj); } static void stop_self(void* obj) { aml_stop(aml_get_default(), obj); } static void defer_client_close(struct nvnc_client* client) { struct aml_idle* idle = aml_idle_new(stop_self, client, do_deferred_client_close); aml_start(aml_get_default(), idle); aml_unref(idle); } static void close_after_write(void* userdata, enum stream_req_status status) { struct nvnc_client* client = userdata; nvnc_log(NVNC_LOG_DEBUG, "close_after_write(%p): ref %d", client, client->ref); stream_close(client->net_stream); /* This is a rather hacky way of making sure that the client object * stays alive while the stream is processing its queue. * TODO: Figure out some better resource management for clients */ defer_client_close(client); } static int handle_unsupported_version(struct nvnc_client* client) { char buffer[256]; client->state = VNC_CLIENT_STATE_ERROR; struct rfb_error_reason* reason = (struct rfb_error_reason*)(buffer + 1); static const char reason_string[] = "Unsupported version\n"; buffer[0] = 0; /* Number of security types is 0 on error */ reason->length = htonl(strlen(reason_string)); strcpy(reason->message, reason_string); size_t len = 1 + sizeof(*reason) + strlen(reason_string); stream_write(client->net_stream, buffer, len, close_after_write, client); return 0; } static int on_version_message(struct nvnc_client* client) { struct nvnc* server = client->server; if (client->buffer_len - client->buffer_index < 12) return 0; char version_string[13]; memcpy(version_string, client->msg_buffer + client->buffer_index, 12); version_string[12] = '\0'; if (strcmp(RFB_VERSION_MESSAGE, version_string) != 0) return handle_unsupported_version(client); uint8_t buf[sizeof(struct rfb_security_types_msg) + SECURITY_TYPES_MAX] = {}; struct rfb_security_types_msg* security = (struct rfb_security_types_msg*)buf; security->n = 0; if (server->auth_flags & NVNC_AUTH_REQUIRE_AUTH) { assert(server->auth_fn); #ifdef ENABLE_TLS if (server->tls_creds) { security->types[security->n++] = RFB_SECURITY_TYPE_VENCRYPT; } #endif #ifdef HAVE_CRYPTO security->types[security->n++] = RFB_SECURITY_TYPE_RSA_AES256; security->types[security->n++] = RFB_SECURITY_TYPE_RSA_AES; if (!(server->auth_flags & NVNC_AUTH_REQUIRE_ENCRYPTION)) { security->types[security->n++] = RFB_SECURITY_TYPE_APPLE_DH; } #endif } else { security->n = 1; security->types[0] = RFB_SECURITY_TYPE_NONE; } if (security->n == 0) { nvnc_log(NVNC_LOG_PANIC, "Failed to satisfy requested security constraints"); } stream_write(client->net_stream, security, sizeof(*security) + security->n, NULL, NULL); client->state = VNC_CLIENT_STATE_WAITING_FOR_SECURITY; return 12; } static int security_handshake_failed(struct nvnc_client* client, const char* username, const char* reason_string) { if (username) nvnc_log(NVNC_LOG_INFO, "Security handshake failed for \"%s\": %s", username, reason_string); else nvnc_log(NVNC_LOG_INFO, "Security handshake: %s", username, reason_string); char buffer[256]; client->state = VNC_CLIENT_STATE_ERROR; uint32_t* result = (uint32_t*)buffer; struct rfb_error_reason* reason = (struct rfb_error_reason*)(buffer + sizeof(*result)); *result = htonl(RFB_SECURITY_HANDSHAKE_FAILED); reason->length = htonl(strlen(reason_string)); strcpy(reason->message, reason_string); size_t len = sizeof(*result) + sizeof(*reason) + strlen(reason_string); stream_write(client->net_stream, buffer, len, close_after_write, client); return 0; } static int security_handshake_ok(struct nvnc_client* client, const char* username) { if (username) { nvnc_log(NVNC_LOG_INFO, "User \"%s\" authenticated", username); strncpy(client->username, username, sizeof(client->username)); client->username[sizeof(client->username) - 1] = '\0'; } uint32_t result = htonl(RFB_SECURITY_HANDSHAKE_OK); return stream_write(client->net_stream, &result, sizeof(result), NULL, NULL); } #ifdef ENABLE_TLS static int send_byte(struct nvnc_client* client, uint8_t value) { return stream_write(client->net_stream, &value, 1, NULL, NULL); } static int send_byte_and_close(struct nvnc_client* client, uint8_t value) { return stream_write(client->net_stream, &value, 1, close_after_write, client); } static int vencrypt_send_version(struct nvnc_client* client) { struct rfb_vencrypt_version_msg msg = { .major = 0, .minor = 2, }; return stream_write(client->net_stream, &msg, sizeof(msg), NULL, NULL); } static int on_vencrypt_version_message(struct nvnc_client* client) { struct rfb_vencrypt_version_msg* msg = (struct rfb_vencrypt_version_msg*)&client->msg_buffer[client->buffer_index]; if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; if (msg->major != 0 || msg->minor != 2) { security_handshake_failed(client, NULL, "Unsupported VeNCrypt version"); return sizeof(*msg); } send_byte(client, 0); struct rfb_vencrypt_subtypes_msg result = { .n = 1, }; result.types[0] = htonl(RFB_VENCRYPT_X509_PLAIN); stream_write(client->net_stream, &result, sizeof(result), NULL, NULL); client->state = VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_SUBTYPE; return sizeof(*msg); } static int on_vencrypt_subtype_message(struct nvnc_client* client) { uint32_t* msg = (uint32_t*)&client->msg_buffer[client->buffer_index]; if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; enum rfb_vencrypt_subtype subtype = ntohl(*msg); if (subtype != RFB_VENCRYPT_X509_PLAIN) { client->state = VNC_CLIENT_STATE_ERROR; send_byte_and_close(client, 0); return sizeof(*msg); } send_byte(client, 1); if (stream_upgrade_to_tls(client->net_stream, client->server->tls_creds) < 0) { client->state = VNC_CLIENT_STATE_ERROR; nvnc_client_close(client); return sizeof(*msg); } client->state = VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH; return sizeof(*msg); } static int on_vencrypt_plain_auth_message(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_vencrypt_plain_auth_msg* msg = (void*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; uint32_t ulen = ntohl(msg->username_len); uint32_t plen = ntohl(msg->password_len); if (client->buffer_len - client->buffer_index < sizeof(*msg) + ulen + plen) return 0; char username[256]; char password[256]; memcpy(username, msg->text, MIN(ulen, sizeof(username) - 1)); memcpy(password, msg->text + ulen, MIN(plen, sizeof(password) - 1)); username[MIN(ulen, sizeof(username) - 1)] = '\0'; password[MIN(plen, sizeof(password) - 1)] = '\0'; if (server->auth_fn(username, password, server->auth_ud)) { security_handshake_ok(client, username); client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT; } else { security_handshake_failed(client, username, "Invalid username or password"); } return sizeof(*msg) + ulen + plen; } #endif #ifdef HAVE_CRYPTO static int apple_dh_send_public_key(struct nvnc_client* client) { client->apple_dh_secret = crypto_keygen(); assert(client->apple_dh_secret); struct crypto_key* pub = crypto_derive_public_key(client->apple_dh_secret); assert(pub); uint8_t mod[APPLE_DH_SERVER_KEY_LENGTH] = {}; int mod_len = crypto_key_p(pub, mod, sizeof(mod)); assert(mod_len == sizeof(mod)); uint8_t q[APPLE_DH_SERVER_KEY_LENGTH] = {}; int q_len = crypto_key_q(pub, q, sizeof(q)); assert(q_len == sizeof(q)); struct rfb_apple_dh_server_msg msg = { .generator = htons(crypto_key_g(client->apple_dh_secret)), .key_size = htons(q_len), }; stream_write(client->net_stream, &msg, sizeof(msg), NULL, NULL); stream_write(client->net_stream, mod, mod_len, NULL, NULL); stream_write(client->net_stream, q, q_len, NULL, NULL); crypto_key_del(pub); return 0; } static int on_apple_dh_response(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_apple_dh_client_msg* msg = (void*)(client->msg_buffer + client->buffer_index); uint8_t p[APPLE_DH_SERVER_KEY_LENGTH]; int key_len = crypto_key_p(client->apple_dh_secret, p, sizeof(p)); assert(key_len == sizeof(p)); if (client->buffer_len - client->buffer_index < sizeof(*msg) + key_len) return 0; int g = crypto_key_g(client->apple_dh_secret); struct crypto_key* remote_key = crypto_key_new(g, p, key_len, msg->public_key, key_len); assert(remote_key); struct crypto_key* shared_secret = crypto_derive_shared_secret(client->apple_dh_secret, remote_key); assert(shared_secret); uint8_t shared_buf[APPLE_DH_SERVER_KEY_LENGTH]; crypto_key_q(shared_secret, shared_buf, sizeof(shared_buf)); crypto_key_del(shared_secret); uint8_t hash[16] = {}; crypto_hash_one(hash, sizeof(hash), CRYPTO_HASH_MD5, shared_buf, sizeof(shared_buf)); struct crypto_cipher* cipher; cipher = crypto_cipher_new(NULL, hash, CRYPTO_CIPHER_AES128_ECB); assert(cipher); char username[128] = {}; char* password = username + 64; crypto_cipher_decrypt(cipher, (uint8_t*)username, NULL, msg->encrypted_credentials, sizeof(username), NULL, 0); username[63] = '\0'; username[127] = '\0'; crypto_cipher_del(cipher); if (server->auth_fn(username, password, server->auth_ud)) { security_handshake_ok(client, username); client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT; } else { security_handshake_failed(client, username, "Invalid username or password"); } return sizeof(*msg) + key_len; } static int rsa_aes_send_public_key(struct nvnc_client* client) { struct nvnc* server = client->server; if (!server->rsa_priv) { assert(!server->rsa_pub); nvnc_log(NVNC_LOG_WARNING, "An RSA key has not been set. A new key will be generated."); server->rsa_priv = crypto_rsa_priv_key_new(); server->rsa_pub = crypto_rsa_pub_key_new(); crypto_rsa_keygen(server->rsa_pub, server->rsa_priv); } assert(server->rsa_pub && server->rsa_priv); size_t key_len = crypto_rsa_pub_key_length(server->rsa_pub); size_t buf_len = sizeof(struct rfb_rsa_aes_pub_key_msg) + key_len * 2; char* buffer = calloc(1, buf_len); assert(buffer); struct rfb_rsa_aes_pub_key_msg* msg = (struct rfb_rsa_aes_pub_key_msg*)buffer; uint8_t* modulus = msg->modulus_and_exponent; uint8_t* exponent = msg->modulus_and_exponent + key_len; msg->length = htonl(key_len * 8); crypto_rsa_pub_key_modulus(server->rsa_pub, modulus, key_len); crypto_rsa_pub_key_exponent(server->rsa_pub, exponent, key_len); stream_send(client->net_stream, rcbuf_new(buffer, buf_len), NULL, NULL); return 0; } static int rsa_aes_send_challenge(struct nvnc_client* client, struct crypto_rsa_pub_key* pub) { crypto_random(client->rsa.challenge, client->rsa.challenge_len); uint8_t buffer[1024]; struct rfb_rsa_aes_challenge_msg *msg = (struct rfb_rsa_aes_challenge_msg*)buffer; ssize_t len = crypto_rsa_encrypt(pub, msg->challenge, crypto_rsa_pub_key_length(client->rsa.pub), client->rsa.challenge, client->rsa.challenge_len); msg->length = htons(len); stream_write(client->net_stream, buffer, sizeof(*msg) + len, NULL, NULL); return 0; } static int on_rsa_aes_public_key(struct nvnc_client* client) { struct rfb_rsa_aes_pub_key_msg* msg = (void*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; uint32_t bit_length = ntohl(msg->length); size_t byte_length = UDIV_UP(bit_length, 8); if (client->buffer_len - client->buffer_index < sizeof(*msg) + byte_length * 2) return 0; const uint8_t* modulus = msg->modulus_and_exponent; const uint8_t* exponent = msg->modulus_and_exponent + byte_length; client->rsa.pub = crypto_rsa_pub_key_import(modulus, exponent, byte_length); assert(client->rsa.pub); client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE; rsa_aes_send_challenge(client, client->rsa.pub); return sizeof(*msg) + byte_length * 2; } static size_t client_rsa_aes_hash_len(const struct nvnc_client* client) { switch (client->rsa.hash_type) { case CRYPTO_HASH_SHA1: return 20; case CRYPTO_HASH_SHA256: return 32; default:; } abort(); return 0; } static int on_rsa_aes_challenge(struct nvnc_client* client) { struct rfb_rsa_aes_challenge_msg* msg = (void*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; uint16_t length = ntohs(msg->length); if (client->buffer_len - client->buffer_index < sizeof(*msg) + length) return 0; struct nvnc* server = client->server; uint8_t client_random[32] = {}; ssize_t len = crypto_rsa_decrypt(server->rsa_priv, client_random, client->rsa.challenge_len, msg->challenge, length); if (len < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to decrypt client's challenge"); client->state = VNC_CLIENT_STATE_ERROR; nvnc_client_close(client); goto done; } // ClientSessionKey = the first 16 bytes of SHA1(ServerRandom || ClientRandom) uint8_t client_session_key[32]; crypto_hash_many(client_session_key, client_rsa_aes_hash_len(client), client->rsa.hash_type, (const struct crypto_data_entry[]) { { client->rsa.challenge, client->rsa.challenge_len }, { client_random, client->rsa.challenge_len }, {} }); // ServerSessionKey = the first 16 bytes of SHA1(ClientRandom || ServerRandom) uint8_t server_session_key[32]; crypto_hash_many(server_session_key, client_rsa_aes_hash_len(client), client->rsa.hash_type, (const struct crypto_data_entry[]) { { client_random, client->rsa.challenge_len }, { client->rsa.challenge, client->rsa.challenge_len }, {} }); stream_upgrade_to_rsa_eas(client->net_stream, client->rsa.cipher_type, server_session_key, client_session_key); size_t server_key_len = crypto_rsa_pub_key_length(server->rsa_pub); uint8_t* server_modulus = malloc(server_key_len * 2); uint8_t* server_exponent = server_modulus + server_key_len; crypto_rsa_pub_key_modulus(server->rsa_pub, server_modulus, server_key_len); crypto_rsa_pub_key_exponent(server->rsa_pub, server_exponent, server_key_len); size_t client_key_len = crypto_rsa_pub_key_length(client->rsa.pub); uint8_t* client_modulus = malloc(client_key_len * 2); uint8_t* client_exponent = client_modulus + client_key_len; crypto_rsa_pub_key_modulus(client->rsa.pub, client_modulus, client_key_len); crypto_rsa_pub_key_exponent(client->rsa.pub, client_exponent, client_key_len); uint32_t server_key_len_be = htonl(server_key_len * 8); uint32_t client_key_len_be = htonl(client_key_len * 8); uint8_t server_hash[32] = {}; crypto_hash_many(server_hash, client_rsa_aes_hash_len(client), client->rsa.hash_type, (const struct crypto_data_entry[]) { { (uint8_t*)&server_key_len_be, 4 }, { server_modulus, server_key_len }, { server_exponent, server_key_len }, { (uint8_t*)&client_key_len_be, 4 }, { client_modulus, client_key_len }, { client_exponent, client_key_len }, {} }); free(server_modulus); free(client_modulus); stream_write(client->net_stream, server_hash, client_rsa_aes_hash_len(client), NULL, NULL); client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH; done: return sizeof(*msg) + length; } static int on_rsa_aes_client_hash(struct nvnc_client* client) { const char* msg = (void*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < client_rsa_aes_hash_len(client)) return 0; struct nvnc* server = client->server; size_t server_key_len = crypto_rsa_pub_key_length(server->rsa_pub); uint8_t* server_modulus = malloc(server_key_len * 2); uint8_t* server_exponent = server_modulus + server_key_len; crypto_rsa_pub_key_modulus(server->rsa_pub, server_modulus, server_key_len); crypto_rsa_pub_key_exponent(server->rsa_pub, server_exponent, server_key_len); size_t client_key_len = crypto_rsa_pub_key_length(client->rsa.pub); uint8_t* client_modulus = malloc(client_key_len * 2); uint8_t* client_exponent = client_modulus + client_key_len; crypto_rsa_pub_key_modulus(client->rsa.pub, client_modulus, client_key_len); crypto_rsa_pub_key_exponent(client->rsa.pub, client_exponent, client_key_len); uint32_t server_key_len_be = htonl(server_key_len * 8); uint32_t client_key_len_be = htonl(client_key_len * 8); uint8_t client_hash[32] = {}; crypto_hash_many(client_hash, client_rsa_aes_hash_len(client), client->rsa.hash_type, (const struct crypto_data_entry[]) { { (uint8_t*)&client_key_len_be, 4 }, { client_modulus, client_key_len }, { client_exponent, client_key_len }, { (uint8_t*)&server_key_len_be, 4 }, { server_modulus, server_key_len }, { server_exponent, server_key_len }, {} }); free(client_modulus); free(server_modulus); if (memcmp(msg, client_hash, client_rsa_aes_hash_len(client)) != 0) { nvnc_log(NVNC_LOG_INFO, "Client hash mismatch"); nvnc_client_close(client); return 0; } // TODO: Read this from config uint8_t subtype = RFB_RSA_AES_CRED_SUBTYPE_USER_AND_PASS; stream_write(client->net_stream, &subtype, 1, NULL, NULL); client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS; return client_rsa_aes_hash_len(client); } static int on_rsa_aes_credentials(struct nvnc_client* client) { const uint8_t* msg = (void*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < 2) return 0; size_t username_len = msg[0]; if (client->buffer_len - client->buffer_index < 2 + username_len) return 0; size_t password_len = msg[1 + username_len]; if (client->buffer_len - client->buffer_index < 2 + username_len + password_len) return 0; struct nvnc* server = client->server; char username[256]; char password[256]; memcpy(username, (const char*)(msg + 1), username_len); username[username_len] = '\0'; memcpy(password, (const char*)(msg + 2 + username_len), password_len); password[password_len] = '\0'; if (server->auth_fn(username, password, server->auth_ud)) { security_handshake_ok(client, username); client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT; } else { security_handshake_failed(client, username, "Invalid username or password"); } return 2 + username_len + password_len; } #endif // HAVE_CRYPTO static int on_security_message(struct nvnc_client* client) { if (client->buffer_len - client->buffer_index < 1) return 0; uint8_t type = client->msg_buffer[client->buffer_index]; nvnc_log(NVNC_LOG_DEBUG, "Client chose security type: %d", type); switch (type) { case RFB_SECURITY_TYPE_NONE: security_handshake_ok(client, NULL); client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT; break; #ifdef ENABLE_TLS case RFB_SECURITY_TYPE_VENCRYPT: vencrypt_send_version(client); client->state = VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION; break; #endif #ifdef HAVE_CRYPTO case RFB_SECURITY_TYPE_APPLE_DH: apple_dh_send_public_key(client); client->state = VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE; break; case RFB_SECURITY_TYPE_RSA_AES: client->rsa.hash_type = CRYPTO_HASH_SHA1; client->rsa.cipher_type = CRYPTO_CIPHER_AES_EAX; client->rsa.challenge_len = 16; rsa_aes_send_public_key(client); client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY; break; case RFB_SECURITY_TYPE_RSA_AES256: client->rsa.hash_type = CRYPTO_HASH_SHA256; client->rsa.cipher_type = CRYPTO_CIPHER_AES256_EAX; client->rsa.challenge_len = 32; rsa_aes_send_public_key(client); client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY; break; #endif default: security_handshake_failed(client, NULL, "Unsupported security type"); break; } return sizeof(type); } static void disconnect_all_other_clients(struct nvnc_client* client) { struct nvnc_client* node; struct nvnc_client* tmp; LIST_FOREACH_SAFE (node, &client->server->clients, link, tmp) if (node != client) { nvnc_log(NVNC_LOG_DEBUG, "disconnect other client %p (ref %d)", node, node->ref); nvnc_client_close(node); } } static void send_server_init_message(struct nvnc_client* client) { struct nvnc* server = client->server; struct nvnc_display* display = server->display; size_t name_len = strlen(server->name); size_t size = sizeof(struct rfb_server_init_msg) + name_len; if (!display) { nvnc_log(NVNC_LOG_WARNING, "Tried to send init message, but no display has been added"); goto close; } if (!display->buffer) { nvnc_log(NVNC_LOG_WARNING, "Tried to send init message, but no framebuffers have been set"); goto close; } uint16_t width = nvnc_fb_get_width(display->buffer); uint16_t height = nvnc_fb_get_height(display->buffer); uint32_t fourcc = nvnc_fb_get_fourcc_format(display->buffer); struct rfb_server_init_msg* msg = calloc(1, size); if (!msg) goto close; msg->width = htons(width); msg->height = htons(height); msg->name_length = htonl(name_len); memcpy(msg->name_string, server->name, name_len); int rc = rfb_pixfmt_from_fourcc(&msg->pixel_format, fourcc); if (rc < 0) goto pixfmt_failure; msg->pixel_format.red_max = htons(msg->pixel_format.red_max); msg->pixel_format.green_max = htons(msg->pixel_format.green_max); msg->pixel_format.blue_max = htons(msg->pixel_format.blue_max); struct rcbuf* payload = rcbuf_new(msg, size); stream_send(client->net_stream, payload, NULL, NULL); client->known_width = width; client->known_height = height; return; pixfmt_failure: free(msg); close: nvnc_client_close(client); } static int on_init_message(struct nvnc_client* client) { if (client->buffer_len - client->buffer_index < 1) return 0; uint8_t shared_flag = client->msg_buffer[client->buffer_index]; if (!shared_flag) disconnect_all_other_clients(client); send_server_init_message(client); nvnc_client_fn fn = client->server->new_client_fn; if (fn) fn(client); client->state = VNC_CLIENT_STATE_READY; return sizeof(shared_flag); } static int cook_pixel_map(struct nvnc_client* client) { struct rfb_pixel_format* fmt = &client->pixfmt; // We'll just pretend that this is rgb332 fmt->true_colour_flag = true; fmt->big_endian_flag = false; fmt->bits_per_pixel = 8; fmt->depth = 8; fmt->red_max = 7; fmt->green_max = 7; fmt->blue_max = 3; fmt->red_shift = 5; fmt->green_shift = 2; fmt->blue_shift = 0; uint8_t buf[sizeof(struct rfb_set_colour_map_entries_msg) + 256 * sizeof(struct rfb_colour_map_entry)]; struct rfb_set_colour_map_entries_msg* msg = (struct rfb_set_colour_map_entries_msg*)buf; make_rgb332_pal8_map(msg); return stream_write(client->net_stream, buf, sizeof(buf), NULL, NULL); } static int on_client_set_pixel_format(struct nvnc_client* client) { if (client->buffer_len - client->buffer_index < 4 + sizeof(struct rfb_pixel_format)) return 0; struct rfb_pixel_format* fmt = (struct rfb_pixel_format*)(client->msg_buffer + client->buffer_index + 4); if (fmt->true_colour_flag) { nvnc_log(NVNC_LOG_DEBUG, "Using color palette for client %p", client); fmt->red_max = ntohs(fmt->red_max); fmt->green_max = ntohs(fmt->green_max); fmt->blue_max = ntohs(fmt->blue_max); memcpy(&client->pixfmt, fmt, sizeof(client->pixfmt)); } else { nvnc_log(NVNC_LOG_DEBUG, "Using color palette for client %p", client); cook_pixel_map(client); } client->has_pixfmt = true; client->formats_changed = true; nvnc_log(NVNC_LOG_DEBUG, "Client %p chose pixel format: %s", client, rfb_pixfmt_to_string(&client->pixfmt)); return 4 + sizeof(struct rfb_pixel_format); } static void encodings_to_string_list(char* dst, size_t len, enum rfb_encodings* encodings, size_t n) { size_t off = 0; if (n > 0) off += snprintf(dst, len, "%s", encoding_to_string(encodings[0])); for (size_t i = 1; i < n; ++i) off += snprintf(dst + off, len - off, ",%s", encoding_to_string(encodings[i])); } static int on_client_set_encodings(struct nvnc_client* client) { struct rfb_client_set_encodings_msg* msg = (struct rfb_client_set_encodings_msg*)(client->msg_buffer + client->buffer_index); size_t n_encodings = ntohs(msg->n_encodings); size_t n = 0; if (client->buffer_len - client->buffer_index < sizeof(*msg) + n_encodings * 4) return 0; client->quality = 10; for (size_t i = 0; i < n_encodings && n < MAX_ENCODINGS; ++i) { enum rfb_encodings encoding = htonl(msg->encodings[i]); switch (encoding) { case RFB_ENCODING_RAW: case RFB_ENCODING_COPYRECT: case RFB_ENCODING_RRE: case RFB_ENCODING_HEXTILE: case RFB_ENCODING_TIGHT: case RFB_ENCODING_TRLE: case RFB_ENCODING_ZRLE: case RFB_ENCODING_OPEN_H264: case RFB_ENCODING_CURSOR: case RFB_ENCODING_DESKTOPSIZE: case RFB_ENCODING_EXTENDEDDESKTOPSIZE: case RFB_ENCODING_QEMU_EXT_KEY_EVENT: case RFB_ENCODING_QEMU_LED_STATE: case RFB_ENCODING_VMWARE_LED_STATE: #ifdef ENABLE_EXPERIMENTAL case RFB_ENCODING_PTS: case RFB_ENCODING_NTP: #endif client->encodings[n++] = encoding; #ifndef ENABLE_EXPERIMENTAL case RFB_ENCODING_PTS: case RFB_ENCODING_NTP: ; #endif } if (RFB_ENCODING_JPEG_LOWQ <= encoding && encoding <= RFB_ENCODING_JPEG_HIGHQ) client->quality = encoding - RFB_ENCODING_JPEG_LOWQ; } char encoding_list[256] = {}; encodings_to_string_list(encoding_list, sizeof(encoding_list), client->encodings, n); nvnc_log(NVNC_LOG_DEBUG, "Client %p set encodings: %s", client, encoding_list); client->n_encodings = n; client->formats_changed = true; return sizeof(*msg) + 4 * n_encodings; } static void send_cursor_update(struct nvnc_client* client) { struct nvnc* server = client->server; struct vec payload; vec_init(&payload, 4096); struct rfb_server_fb_update_msg head = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(1), }; vec_append(&payload, &head, sizeof(head)); int rc = cursor_encode(&payload, &client->pixfmt, server->cursor.buffer, server->cursor.width, server->cursor.height, server->cursor.hotspot_x, server->cursor.hotspot_y); if (rc < 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to send cursor to client"); vec_destroy(&payload); return; } client->cursor_seq = server->cursor_seq; stream_send(client->net_stream, rcbuf_new(payload.data, payload.len), NULL, NULL); } static bool will_send_pts(const struct nvnc_client* client, uint64_t pts) { return pts != NVNC_NO_PTS && client_has_encoding(client, RFB_ENCODING_PTS); } static int send_pts_rect(struct nvnc_client* client, uint64_t pts) { if (!will_send_pts(client, pts)) return 0; uint8_t buf[sizeof(struct rfb_server_fb_rect) + 8] = { 0 }; struct rfb_server_fb_rect* head = (struct rfb_server_fb_rect*)buf; head->encoding = htonl(RFB_ENCODING_PTS); uint64_t* msg_pts = (uint64_t*)&buf[sizeof(struct rfb_server_fb_rect)]; *msg_pts = nvnc__htonll(pts); return stream_write(client->net_stream, buf, sizeof(buf), NULL, NULL); } static const char* encoding_to_string(enum rfb_encodings encoding) { switch (encoding) { case RFB_ENCODING_RAW: return "raw"; case RFB_ENCODING_COPYRECT: return "copyrect"; case RFB_ENCODING_RRE: return "rre"; case RFB_ENCODING_HEXTILE: return "hextile"; case RFB_ENCODING_TIGHT: return "tight"; case RFB_ENCODING_TRLE: return "trle"; case RFB_ENCODING_ZRLE: return "zrle"; case RFB_ENCODING_OPEN_H264: return "open-h264"; case RFB_ENCODING_CURSOR: return "cursor"; case RFB_ENCODING_DESKTOPSIZE: return "desktop-size"; case RFB_ENCODING_EXTENDEDDESKTOPSIZE: return "extended-desktop-size"; case RFB_ENCODING_QEMU_EXT_KEY_EVENT: return "qemu-extended-key-event"; case RFB_ENCODING_QEMU_LED_STATE: return "qemu-led-state"; case RFB_ENCODING_VMWARE_LED_STATE: return "vmware-led-state"; case RFB_ENCODING_PTS: return "pts"; case RFB_ENCODING_NTP: return "ntp"; } return "UNKNOWN"; } static bool ensure_encoder(struct nvnc_client* client, const struct nvnc_fb *fb) { struct nvnc* server = client->server; enum rfb_encodings encoding = choose_frame_encoding(client, fb); if (client->encoder && encoding == encoder_get_type(client->encoder)) return true; int width = server->display->buffer->width; int height = server->display->buffer->height; if (client->encoder) { server->n_damage_clients -= !(client->encoder->impl->flags & ENCODER_IMPL_FLAG_IGNORES_DAMAGE); client->encoder->on_done = NULL; } encoder_unref(client->encoder); /* Zlib streams need to be saved so we keep encoders around that * use them. */ switch (encoding) { case RFB_ENCODING_ZRLE: if (!client->zrle_encoder) { client->zrle_encoder = encoder_new(encoding, width, height); } client->encoder = client->zrle_encoder; encoder_ref(client->encoder); break; case RFB_ENCODING_TIGHT: if (!client->tight_encoder) { client->tight_encoder = encoder_new(encoding, width, height); } client->encoder = client->tight_encoder; encoder_ref(client->encoder); break; default: client->encoder = encoder_new(encoding, width, height); break; } if (!client->encoder) { nvnc_log(NVNC_LOG_ERROR, "Failed to allocate new encoder"); return false; } server->n_damage_clients += !(client->encoder->impl->flags & ENCODER_IMPL_FLAG_IGNORES_DAMAGE); nvnc_log(NVNC_LOG_INFO, "Choosing %s encoding for client %p", encoding_to_string(encoding), client); return true; } static void process_fb_update_requests(struct nvnc_client* client) { struct nvnc* server = client->server; if (!server->display || !server->display->buffer) return; if (client->net_stream->state == STREAM_STATE_CLOSED) return; if (client->is_updating || client->n_pending_requests == 0) return; struct nvnc_fb* fb = client->server->display->buffer; assert(fb); if (!client->has_pixfmt) { rfb_pixfmt_from_fourcc(&client->pixfmt, fb->fourcc_format); client->has_pixfmt = true; } if (fb->width != client->known_width || fb->height != client->known_height) { send_desktop_resize(client, fb); if (--client->n_pending_requests <= 0) return; } if (!client->is_ext_notified) { client->is_ext_notified = true; if (send_ext_support_frame(client)) { if (--client->n_pending_requests <= 0) return; } } if (server->cursor_seq != client->cursor_seq && client_has_encoding(client, RFB_ENCODING_CURSOR)) { send_cursor_update(client); if (--client->n_pending_requests <= 0) return; } if (client_send_led_state(client)) { if (--client->n_pending_requests <= 0) return; } if (!pixman_region_not_empty(&client->damage)) return; if (!ensure_encoder(client, fb)) return; DTRACE_PROBE1(neatvnc, update_fb_start, client); /* The client's damage is exchanged for an empty one */ struct pixman_region16 damage = client->damage; pixman_region_init(&client->damage); client->is_updating = true; client->formats_changed = false; client->current_fb = fb; nvnc_fb_hold(fb); nvnc_fb_ref(fb); client_ref(client); encoder_set_quality(client->encoder, client->quality); encoder_set_output_format(client->encoder, &client->pixfmt); client->encoder->on_done = on_encode_frame_done; client->encoder->userdata = client; DTRACE_PROBE2(neatvnc, process_fb_update_requests__encode, client, fb->pts); if (encoder_encode(client->encoder, fb, &damage) >= 0) { --client->n_pending_requests; } else { nvnc_log(NVNC_LOG_ERROR, "Failed to encode current frame"); client_unref(client); client->is_updating = false; client->formats_changed = false; assert(client->current_fb); nvnc_fb_release(client->current_fb); nvnc_fb_unref(client->current_fb); client->current_fb = NULL; } pixman_region_fini(&damage); } static int on_client_fb_update_request(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_client_fb_update_req_msg* msg = (struct rfb_client_fb_update_req_msg*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; int incremental = msg->incremental; int x = ntohs(msg->x); int y = ntohs(msg->y); int width = ntohs(msg->width); int height = ntohs(msg->height); client->n_pending_requests++; /* Note: The region sent from the client is ignored for incremental * updates. This avoids superfluous complexity. */ if (!incremental) { pixman_region_union_rect(&client->damage, &client->damage, x, y, width, height); if (client->encoder) encoder_request_key_frame(client->encoder); } DTRACE_PROBE1(neatvnc, update_fb_request, client); nvnc_fb_req_fn fn = server->fb_req_fn; if (fn) fn(client, incremental, x, y, width, height); if (!incremental && client_has_encoding(client, RFB_ENCODING_EXTENDEDDESKTOPSIZE)) { client->known_width = 0; client->known_height = 0; } process_fb_update_requests(client); return sizeof(*msg); } static int on_client_key_event(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_client_key_event_msg* msg = (struct rfb_client_key_event_msg*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; int down_flag = msg->down_flag; uint32_t keysym = ntohl(msg->key); nvnc_key_fn fn = server->key_fn; if (fn) fn(client, keysym, !!down_flag); return sizeof(*msg); } static int on_client_qemu_key_event(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_client_qemu_key_event_msg* msg = (struct rfb_client_qemu_key_event_msg*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; int down_flag = msg->down_flag; uint32_t xt_keycode = ntohl(msg->keycode); uint32_t evdev_keycode = code_map_qnum_to_linux[xt_keycode]; if (!evdev_keycode) evdev_keycode = xt_keycode; nvnc_key_fn fn = server->key_code_fn; if (fn) fn(client, evdev_keycode, !!down_flag); return sizeof(*msg); } static int on_client_qemu_event(struct nvnc_client* client) { if (client->buffer_len - client->buffer_index < 2) return 0; enum rfb_client_to_server_qemu_msg_type subtype = client->msg_buffer[client->buffer_index + 1]; switch (subtype) { case RFB_CLIENT_TO_SERVER_QEMU_KEY_EVENT: return on_client_qemu_key_event(client); } nvnc_log(NVNC_LOG_WARNING, "Got uninterpretable qemu message from client: %p (ref %d)", client, client->ref); nvnc_client_close(client); return 0; } static int on_client_pointer_event(struct nvnc_client* client) { struct nvnc* server = client->server; struct rfb_client_pointer_event_msg* msg = (struct rfb_client_pointer_event_msg*)(client->msg_buffer + client->buffer_index); if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; int button_mask = msg->button_mask; uint16_t x = ntohs(msg->x); uint16_t y = ntohs(msg->y); nvnc_pointer_fn fn = server->pointer_fn; if (fn) fn(client, x, y, button_mask); return sizeof(*msg); } EXPORT void nvnc_send_cut_text(struct nvnc* server, const char* text, uint32_t len) { struct rfb_cut_text_msg msg; msg.type = RFB_SERVER_TO_CLIENT_SERVER_CUT_TEXT; msg.length = htonl(len); struct nvnc_client* client; LIST_FOREACH (client, &server->clients, link) { stream_write(client->net_stream, &msg, sizeof(msg), NULL, NULL); stream_write(client->net_stream, text, len, NULL, NULL); } } static int on_client_cut_text(struct nvnc_client* client) { struct rfb_cut_text_msg* msg = (struct rfb_cut_text_msg*)(client->msg_buffer + client->buffer_index); size_t left_to_process = client->buffer_len - client->buffer_index; if (left_to_process < sizeof(*msg)) return 0; uint32_t length = ntohl(msg->length); uint32_t max_length = MAX_CUT_TEXT_SIZE; /* Messages greater than this size are unsupported */ if (length > max_length) { nvnc_log(NVNC_LOG_ERROR, "Copied text length (%d) is greater than max supported length (%d)", length, max_length); nvnc_client_close(client); return 0; } size_t msg_size = sizeof(*msg) + length; if (msg_size <= left_to_process) { nvnc_cut_text_fn fn = client->server->cut_text_fn; if (fn) fn(client, msg->text, length); return msg_size; } assert(!client->cut_text.buffer); client->cut_text.buffer = malloc(length); if (!client->cut_text.buffer) { nvnc_log(NVNC_LOG_ERROR, "OOM: %m"); nvnc_client_close(client); return 0; } size_t partial_size = left_to_process - sizeof(*msg); memcpy(client->cut_text.buffer, msg->text, partial_size); client->cut_text.length = length; client->cut_text.index = partial_size; return left_to_process; } static void process_big_cut_text(struct nvnc_client* client) { assert(client->cut_text.length > client->cut_text.index); void* start = client->cut_text.buffer + client->cut_text.index; size_t space = client->cut_text.length - client->cut_text.index; space = MIN(space, MSG_BUFFER_SIZE); ssize_t n_read = stream_read(client->net_stream, start, space); if (n_read == 0) return; if (n_read < 0) { if (errno != EAGAIN) { nvnc_log(NVNC_LOG_INFO, "Client connection error: %p (ref %d)", client, client->ref); nvnc_client_close(client); } return; } client->cut_text.index += n_read; if (client->cut_text.index != client->cut_text.length) return; nvnc_cut_text_fn fn = client->server->cut_text_fn; if (fn) fn(client, client->cut_text.buffer, client->cut_text.length); free(client->cut_text.buffer); client->cut_text.buffer = NULL; } static enum rfb_resize_status check_desktop_layout(struct nvnc_client* client, uint16_t width, uint16_t height, uint8_t n_screens, struct rfb_screen* screens) { struct nvnc* server = client->server; struct nvnc_desktop_layout* layout; enum rfb_resize_status status = RFB_RESIZE_STATUS_SUCCESS; layout = malloc(sizeof(*layout) + n_screens * sizeof(*layout->display_layouts)); if (!layout) return RFB_RESIZE_STATUS_OUT_OF_RESOURCES; layout->width = width; layout->height = height; layout->n_display_layouts = n_screens; for (size_t i = 0; i < n_screens; ++i) { struct nvnc_display_layout* display; struct rfb_screen* screen; display = &layout->display_layouts[i]; screen = &screens[i]; nvnc_display_layout_init(display, screen); if (screen->id == 0) display->display = server->display; if (display->x_pos + display->width > width || display->y_pos + display->height > height) { status = RFB_RESIZE_STATUS_INVALID_LAYOUT; goto out; } } if (!server->desktop_layout_fn || !server->desktop_layout_fn(client, layout)) status = RFB_RESIZE_STATUS_PROHIBITED; out: free(layout); return status; } static void send_extended_desktop_size(struct nvnc_client* client, enum rfb_resize_initiator initiator, enum rfb_resize_status status) { struct rfb_server_fb_update_msg head = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(1), }; struct rfb_server_fb_rect rect = { .encoding = htonl(RFB_ENCODING_EXTENDEDDESKTOPSIZE), .x = htons(initiator), .y = htons(status), .width = htons(client->known_width), .height = htons(client->known_height), }; uint8_t number_of_screens = 1; uint8_t buf[4] = { number_of_screens }; struct rfb_screen screen = { .width = htons(client->known_width), .height = htons(client->known_height), }; stream_write(client->net_stream, &head, sizeof(head), NULL, NULL); stream_write(client->net_stream, &rect, sizeof(rect), NULL, NULL); stream_write(client->net_stream, &buf, sizeof(buf), NULL, NULL); stream_write(client->net_stream, &screen, sizeof(screen), NULL, NULL); } static int on_client_set_desktop_size_event(struct nvnc_client* client) { struct rfb_client_set_desktop_size_event_msg* msg; enum rfb_resize_status status; uint16_t width, height; if (client->buffer_len - client->buffer_index < sizeof(*msg)) return 0; msg = (struct rfb_client_set_desktop_size_event_msg*) (client->msg_buffer + client->buffer_index); width = ntohs(msg->width); height = ntohs(msg->height); status = check_desktop_layout(client, width, height, msg->number_of_screens, msg->screens); send_extended_desktop_size(client, RFB_RESIZE_INITIATOR_THIS_CLIENT, status); return sizeof(*msg) + msg->number_of_screens * sizeof(struct rfb_screen); } static void update_ntp_stats(struct nvnc_client* client, const struct rfb_ntp_msg *msg) { uint32_t t0 = ntohl(msg->t0); uint32_t t1 = ntohl(msg->t1); uint32_t t2 = ntohl(msg->t2); uint32_t t3 = ntohl(msg->t3); double delta = (int32_t)(t3 - t0) - (int32_t)(t2 - t1); double theta = ((int32_t)(t1 - t0) + (int32_t)(t2 - t3)) / 2; nvnc_log(NVNC_LOG_DEBUG, "NTP: delta: %.2f ms, theta: %.2f ms", delta / 1e3, theta / 1e3); } static struct rcbuf* on_ntp_msg_send(struct stream* tcp_stream, void* userdata) { struct rfb_ntp_msg* msg = userdata; msg->t2 = htonl(gettime_us(CLOCK_MONOTONIC)); return rcbuf_from_mem(msg, sizeof(*msg)); } static int on_client_ntp(struct nvnc_client* client) { struct rfb_ntp_msg msg; if (client->buffer_len - client->buffer_index < sizeof(msg)) return 0; memcpy(&msg, client->msg_buffer + client->buffer_index, sizeof(msg)); if (msg.t3 != 0) { update_ntp_stats(client, &msg); return sizeof(msg); } msg.t1 = htonl(gettime_us(CLOCK_MONOTONIC)); struct rfb_ntp_msg* out_msg = malloc(sizeof(*out_msg)); assert(out_msg); memcpy(out_msg, &msg, sizeof(*out_msg)); // The callback gets executed as the message is leaving the send queue // so that we can set t2 as late as possible. stream_exec_and_send(client->net_stream, on_ntp_msg_send, out_msg); return sizeof(msg); } static int on_client_message(struct nvnc_client* client) { if (client->buffer_len - client->buffer_index < 1) return 0; enum rfb_client_to_server_msg_type type = client->msg_buffer[client->buffer_index]; switch (type) { case RFB_CLIENT_TO_SERVER_SET_PIXEL_FORMAT: return on_client_set_pixel_format(client); case RFB_CLIENT_TO_SERVER_SET_ENCODINGS: return on_client_set_encodings(client); case RFB_CLIENT_TO_SERVER_FRAMEBUFFER_UPDATE_REQUEST: return on_client_fb_update_request(client); case RFB_CLIENT_TO_SERVER_KEY_EVENT: return on_client_key_event(client); case RFB_CLIENT_TO_SERVER_POINTER_EVENT: return on_client_pointer_event(client); case RFB_CLIENT_TO_SERVER_CLIENT_CUT_TEXT: return on_client_cut_text(client); case RFB_CLIENT_TO_SERVER_QEMU: return on_client_qemu_event(client); case RFB_CLIENT_TO_SERVER_SET_DESKTOP_SIZE: return on_client_set_desktop_size_event(client); case RFB_CLIENT_TO_SERVER_NTP: return on_client_ntp(client); } nvnc_log(NVNC_LOG_WARNING, "Got uninterpretable message from client: %p (ref %d)", client, client->ref); nvnc_client_close(client); return 0; } static int try_read_client_message(struct nvnc_client* client) { switch (client->state) { case VNC_CLIENT_STATE_ERROR: return client->buffer_len - client->buffer_index; case VNC_CLIENT_STATE_WAITING_FOR_VERSION: return on_version_message(client); case VNC_CLIENT_STATE_WAITING_FOR_SECURITY: return on_security_message(client); case VNC_CLIENT_STATE_WAITING_FOR_INIT: return on_init_message(client); #ifdef ENABLE_TLS case VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION: return on_vencrypt_version_message(client); case VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_SUBTYPE: return on_vencrypt_subtype_message(client); case VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH: return on_vencrypt_plain_auth_message(client); #endif #ifdef HAVE_CRYPTO case VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE: return on_apple_dh_response(client); case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY: return on_rsa_aes_public_key(client); case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE: return on_rsa_aes_challenge(client); case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH: return on_rsa_aes_client_hash(client); case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS: return on_rsa_aes_credentials(client); #endif case VNC_CLIENT_STATE_READY: return on_client_message(client); } nvnc_log(NVNC_LOG_PANIC, "Invalid client state"); return 0; } static void on_client_event(struct stream* stream, enum stream_event event) { struct nvnc_client* client = stream->userdata; assert(client->net_stream && client->net_stream == stream); if (event == STREAM_EVENT_REMOTE_CLOSED) { nvnc_log(NVNC_LOG_INFO, "Client %p (%d) hung up", client, client->ref); defer_client_close(client); return; } if (client->cut_text.buffer) { process_big_cut_text(client); return; } assert(client->buffer_index == 0); void* start = client->msg_buffer + client->buffer_len; size_t space = MSG_BUFFER_SIZE - client->buffer_len; ssize_t n_read = stream_read(stream, start, space); if (n_read == 0) return; if (n_read < 0) { if (errno != EAGAIN) { nvnc_log(NVNC_LOG_INFO, "Client connection error: %p (ref %d)", client, client->ref); nvnc_client_close(client); } return; } client->buffer_len += n_read; while (1) { int rc = try_read_client_message(client); if (rc == 0) break; client->buffer_index += rc; } assert(client->buffer_index <= client->buffer_len); client->buffer_len -= client->buffer_index; memmove(client->msg_buffer, client->msg_buffer + client->buffer_index, client->buffer_len); client->buffer_index = 0; } static void on_connection(void* obj) { struct nvnc* server = aml_get_userdata(obj); struct nvnc_client* client = calloc(1, sizeof(*client)); if (!client) return; client->ref = 1; client->server = server; client->quality = 10; /* default to lossless */ client->led_state = -1; /* trigger sending of initial state */ int fd = accept(server->fd, NULL, 0); if (fd < 0) { nvnc_log(NVNC_LOG_WARNING, "Failed to accept a connection"); goto accept_failure; } int one = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); #ifdef ENABLE_WEBSOCKET if (server->socket_type == NVNC__SOCKET_WEBSOCKET) { client->net_stream = stream_ws_new(fd, on_client_event, client); } else #endif { client->net_stream = stream_new(fd, on_client_event, client); } if (!client->net_stream) { nvnc_log(NVNC_LOG_WARNING, "OOM"); goto stream_failure; } if (!server->display->buffer) { nvnc_log(NVNC_LOG_WARNING, "No display buffer has been set"); goto buffer_failure; } pixman_region_init(&client->damage); struct rcbuf* payload = rcbuf_from_string(RFB_VERSION_MESSAGE); if (!payload) { nvnc_log(NVNC_LOG_WARNING, "OOM"); goto payload_failure; } stream_send(client->net_stream, payload, NULL, NULL); LIST_INSERT_HEAD(&server->clients, client, link); client->state = VNC_CLIENT_STATE_WAITING_FOR_VERSION; char ip_address[256]; struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); nvnc_client_get_address(client, (struct sockaddr*)&addr, &addrlen); sockaddr_to_string(ip_address, sizeof(ip_address), (struct sockaddr*)&addr); nvnc_log(NVNC_LOG_INFO, "New client connection from %s: %p (ref %d)", ip_address, client, client->ref); return; payload_failure: pixman_region_fini(&client->damage); buffer_failure: stream_destroy(client->net_stream); stream_failure: close(fd); accept_failure: free(client); } static void sockaddr_to_string(char* dst, size_t sz, const struct sockaddr* addr) { struct sockaddr_in *sa_in = (struct sockaddr_in*)addr; struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6*)addr; switch (addr->sa_family) { case AF_INET: inet_ntop(addr->sa_family, &sa_in->sin_addr, dst, sz); break; case AF_INET6: inet_ntop(addr->sa_family, &sa_in6->sin6_addr, dst, sz); break; default: nvnc_log(NVNC_LOG_DEBUG, "Don't know how to convert sa_family %d to string", addr->sa_family); break; } } static int bind_address_tcp(const char* name, int port) { struct addrinfo hints = { .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE, }; struct addrinfo* result; char service[256]; snprintf(service, sizeof(service), "%d", port); int rc = getaddrinfo(name, service, &hints, &result); if (rc != 0) { nvnc_log(NVNC_LOG_ERROR, "Failed to get address info: %s", gai_strerror(rc)); return -1; } int fd = -1; for (struct addrinfo* p = result; p != NULL; p = p->ai_next) { char ai_str[256] = { 0 }; sockaddr_to_string(ai_str, sizeof(ai_str), p->ai_addr); nvnc_log(NVNC_LOG_DEBUG, "Trying address: %s", ai_str); fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (fd < 0) { nvnc_log(NVNC_LOG_DEBUG, "Failed to create socket: %m"); continue; } int one = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) { nvnc_log(NVNC_LOG_DEBUG, "Failed to set SO_REUSEADDR: %m"); goto failure; } int sndbuf = 65536; if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0) { nvnc_log(NVNC_LOG_DEBUG, "Failed to set SO_SNDBUF: %m"); } if (bind(fd, p->ai_addr, p->ai_addrlen) == 0) { nvnc_log(NVNC_LOG_DEBUG, "Successfully bound to address"); break; } nvnc_log(NVNC_LOG_DEBUG, "Failed to bind to address: %m"); failure: close(fd); fd = -1; } freeaddrinfo(result); return fd; } static int bind_address_unix(const char* name) { struct sockaddr_un addr = { .sun_family = AF_UNIX, }; if (strlen(name) >= sizeof(addr.sun_path)) { errno = ENAMETOOLONG; return -1; } strcpy(addr.sun_path, name); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { close(fd); return -1; } return fd; } static int bind_address(const char* name, uint16_t port, enum nvnc__socket_type type) { switch (type) { case NVNC__SOCKET_TCP: case NVNC__SOCKET_WEBSOCKET: return bind_address_tcp(name, port); case NVNC__SOCKET_UNIX: return bind_address_unix(name); } nvnc_log(NVNC_LOG_PANIC, "Unknown socket address type"); return -1; } static struct nvnc* open_common(const char* address, uint16_t port, enum nvnc__socket_type type) { nvnc__log_init(); aml_require_workers(aml_get_default(), -1); struct nvnc* self = calloc(1, sizeof(*self)); if (!self) return NULL; self->socket_type = type; strcpy(self->name, DEFAULT_NAME); LIST_INIT(&self->clients); self->fd = bind_address(address, port, type); if (self->fd < 0) goto bind_failure; if (listen(self->fd, 16) < 0) goto listen_failure; self->poll_handle = aml_handler_new(self->fd, on_connection, self, NULL); if (!self->poll_handle) goto handle_failure; if (aml_start(aml_get_default(), self->poll_handle) < 0) goto poll_start_failure; return self; poll_start_failure: aml_unref(self->poll_handle); handle_failure: listen_failure: close(self->fd); if (type == NVNC__SOCKET_UNIX) { unlink(address); } bind_failure: free(self); return NULL; } EXPORT struct nvnc* nvnc_open(const char* address, uint16_t port) { return open_common(address, port, NVNC__SOCKET_TCP); } EXPORT struct nvnc* nvnc_open_websocket(const char *address, uint16_t port) { #ifdef ENABLE_WEBSOCKET return open_common(address, port, NVNC__SOCKET_WEBSOCKET); #else return NULL; #endif } EXPORT struct nvnc* nvnc_open_unix(const char* address) { return open_common(address, 0, NVNC__SOCKET_UNIX); } static void unlink_fd_path(int fd) { struct sockaddr_un addr; socklen_t addr_len = sizeof(addr); if (getsockname(fd, (struct sockaddr*)&addr, &addr_len) == 0) { if (addr.sun_family == AF_UNIX) { unlink(addr.sun_path); } } } EXPORT void nvnc_close(struct nvnc* self) { struct nvnc_client* client; nvnc_cleanup_fn cleanup = self->common.cleanup_fn; if (cleanup) cleanup(self->common.userdata); if (self->display) nvnc_display_unref(self->display); if (self->cursor.buffer) nvnc_fb_unref(self->cursor.buffer); struct nvnc_client* tmp; LIST_FOREACH_SAFE (client, &self->clients, link, tmp) client_unref(client); aml_stop(aml_get_default(), self->poll_handle); unlink_fd_path(self->fd); close(self->fd); #ifdef HAVE_CRYPTO crypto_rsa_priv_key_del(self->rsa_priv); crypto_rsa_pub_key_del(self->rsa_pub); #endif #ifdef ENABLE_TLS if (self->tls_creds) { gnutls_certificate_free_credentials(self->tls_creds); gnutls_global_deinit(); } #endif aml_unref(self->poll_handle); free(self); } static void complete_fb_update(struct nvnc_client* client) { if (!client->is_updating) return; client->is_updating = false; assert(client->current_fb); nvnc_fb_release(client->current_fb); nvnc_fb_unref(client->current_fb); client->current_fb = NULL; process_fb_update_requests(client); client_unref(client); DTRACE_PROBE1(neatvnc, update_fb_done, client); } static void on_write_frame_done(void* userdata, enum stream_req_status status) { struct nvnc_client* client = userdata; complete_fb_update(client); } static enum rfb_encodings choose_frame_encoding(struct nvnc_client* client, const struct nvnc_fb* fb) { for (size_t i = 0; i < client->n_encodings; ++i) switch (client->encodings[i]) { case RFB_ENCODING_RAW: case RFB_ENCODING_TIGHT: case RFB_ENCODING_ZRLE: return client->encodings[i]; #ifdef ENABLE_OPEN_H264 case RFB_ENCODING_OPEN_H264: // h264 is useless for sw frames if (fb->type != NVNC_FB_GBM_BO) break; return client->encodings[i]; #endif default: break; } return RFB_ENCODING_RAW; } static bool client_has_encoding(const struct nvnc_client* client, enum rfb_encodings encoding) { for (size_t i = 0; i < client->n_encodings; ++i) if (client->encodings[i] == encoding) return true; return false; } static void finish_fb_update(struct nvnc_client* client, struct rcbuf* payload, int n_rects, uint64_t pts) { client_ref(client); if (client->net_stream->state == STREAM_STATE_CLOSED) goto complete; if (client->formats_changed) { /* Client has requested new pixel format or encoding in the * meantime, so it probably won't know what to do with this * frame. Pending requests get incremented because this one is * dropped. */ nvnc_log(NVNC_LOG_DEBUG, "Client changed pixel format or encoding with in-flight buffer"); client->n_pending_requests++; goto complete; } DTRACE_PROBE2(neatvnc, send_fb_start, client, pts); n_rects += will_send_pts(client, pts) ? 1 : 0; struct rfb_server_fb_update_msg update_msg = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(n_rects), }; if (stream_write(client->net_stream, &update_msg, sizeof(update_msg), NULL, NULL) < 0) goto complete; if (send_pts_rect(client, pts) < 0) goto complete; rcbuf_ref(payload); if (stream_send(client->net_stream, payload, on_write_frame_done, client) < 0) goto complete; DTRACE_PROBE2(neatvnc, send_fb_done, client, pts); return; complete: complete_fb_update(client); } static void on_encode_frame_done(struct encoder* encoder, struct rcbuf* result, uint64_t pts) { struct nvnc_client* client = encoder->userdata; client->encoder->on_done = NULL; client->encoder->userdata = NULL; finish_fb_update(client, result, encoder->n_rects, pts); client_unref(client); } static int send_desktop_resize(struct nvnc_client* client, struct nvnc_fb* fb) { if (!client_has_encoding(client, RFB_ENCODING_DESKTOPSIZE) && !client_has_encoding(client, RFB_ENCODING_EXTENDEDDESKTOPSIZE)) { nvnc_log(NVNC_LOG_ERROR, "Client does not support desktop resizing. Closing connection..."); nvnc_client_close(client); return -1; } client->known_width = fb->width; client->known_height = fb->height; if (client->encoder) encoder_resize(client->encoder, fb->width, fb->height); pixman_region_union_rect(&client->damage, &client->damage, 0, 0, fb->width, fb->height); if (client_has_encoding(client, RFB_ENCODING_EXTENDEDDESKTOPSIZE)) { send_extended_desktop_size(client, RFB_RESIZE_INITIATOR_SERVER, RFB_RESIZE_STATUS_SUCCESS); return 0; } struct rfb_server_fb_update_msg head = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(1), }; struct rfb_server_fb_rect rect = { .encoding = htonl(RFB_ENCODING_DESKTOPSIZE), .width = htons(fb->width), .height = htons(fb->height), }; stream_write(client->net_stream, &head, sizeof(head), NULL, NULL); stream_write(client->net_stream, &rect, sizeof(rect), NULL, NULL); return 0; } static bool send_ext_support_frame(struct nvnc_client* client) { int has_qemu_ext = client_has_encoding(client, RFB_ENCODING_QEMU_EXT_KEY_EVENT); int has_ntp = client_has_encoding(client, RFB_ENCODING_NTP); int n_rects = has_qemu_ext + has_ntp; if (n_rects == 0) return false; struct rfb_server_fb_update_msg head = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(n_rects), }; stream_write(client->net_stream, &head, sizeof(head), NULL, NULL); if (has_qemu_ext) { struct rfb_server_fb_rect rect = { .encoding = htonl(RFB_ENCODING_QEMU_EXT_KEY_EVENT), }; stream_write(client->net_stream, &rect, sizeof(rect), NULL, NULL); } if (has_ntp) { struct rfb_server_fb_rect rect = { .encoding = htonl(RFB_ENCODING_NTP), }; stream_write(client->net_stream, &rect, sizeof(rect), NULL, NULL); } return true; } void nvnc__damage_region(struct nvnc* self, const struct pixman_region16* damage) { struct nvnc_client* client; LIST_FOREACH(client, &self->clients, link) if (client->net_stream->state != STREAM_STATE_CLOSED) pixman_region_union(&client->damage, &client->damage, (struct pixman_region16*)damage); LIST_FOREACH(client, &self->clients, link) process_fb_update_requests(client); } EXPORT void nvnc_set_userdata(void* self, void* userdata, nvnc_cleanup_fn cleanup_fn) { struct nvnc_common* common = self; common->userdata = userdata; common->cleanup_fn = cleanup_fn; } EXPORT void* nvnc_get_userdata(const void* self) { const struct nvnc_common* common = self; return common->userdata; } EXPORT void nvnc_set_key_fn(struct nvnc* self, nvnc_key_fn fn) { self->key_fn = fn; } EXPORT void nvnc_set_key_code_fn(struct nvnc* self, nvnc_key_fn fn) { self->key_code_fn = fn; } EXPORT void nvnc_set_pointer_fn(struct nvnc* self, nvnc_pointer_fn fn) { self->pointer_fn = fn; } EXPORT void nvnc_set_fb_req_fn(struct nvnc* self, nvnc_fb_req_fn fn) { self->fb_req_fn = fn; } EXPORT void nvnc_set_new_client_fn(struct nvnc* self, nvnc_client_fn fn) { self->new_client_fn = fn; } EXPORT void nvnc_set_client_cleanup_fn(struct nvnc_client* self, nvnc_client_fn fn) { self->cleanup_fn = fn; } EXPORT void nvnc_set_cut_text_fn(struct nvnc* self, nvnc_cut_text_fn fn) { self->cut_text_fn = fn; } EXPORT void nvnc_set_desktop_layout_fn(struct nvnc* self, nvnc_desktop_layout_fn fn) { self->desktop_layout_fn = fn; } EXPORT void nvnc_add_display(struct nvnc* self, struct nvnc_display* display) { if (self->display) { nvnc_log(NVNC_LOG_PANIC, "Multiple displays are not implemented. Aborting!"); } display->server = self; self->display = display; nvnc_display_ref(display); } EXPORT void nvnc_remove_display(struct nvnc* self, struct nvnc_display* display) { if (self->display != display) return; nvnc_display_unref(display); self->display = NULL; } EXPORT struct nvnc* nvnc_client_get_server(const struct nvnc_client* client) { return client->server; } EXPORT int nvnc_client_get_address(const struct nvnc_client* client, struct sockaddr* restrict addr, socklen_t* restrict addrlen) { return getpeername(client->net_stream->fd, addr, addrlen); } EXPORT const char* nvnc_client_get_auth_username(const struct nvnc_client* client) { if (client->username[0] == '\0') return NULL; return client->username; } EXPORT struct nvnc_client* nvnc_client_first(struct nvnc* self) { return LIST_FIRST(&self->clients); } EXPORT struct nvnc_client* nvnc_client_next(struct nvnc_client* client) { assert(client); return LIST_NEXT(client, link); } EXPORT void nvnc_client_close(struct nvnc_client* client) { stream_close(client->net_stream); client_unref(client); } EXPORT bool nvnc_client_supports_cursor(const struct nvnc_client* client) { for (size_t i = 0; i < client->n_encodings; ++i) { if (client->encodings[i] == RFB_ENCODING_CURSOR) return true; } return false; } static bool client_send_led_state(struct nvnc_client* client) { if (client->pending_led_state == client->led_state) return false; bool have_qemu_led_state = client_has_encoding(client, RFB_ENCODING_QEMU_LED_STATE); bool have_vmware_led_state = client_has_encoding(client, RFB_ENCODING_VMWARE_LED_STATE); if (!have_qemu_led_state && !have_vmware_led_state) return false; nvnc_log(NVNC_LOG_DEBUG, "Keyboard LED state changed: %x -> %x", client->led_state, client->pending_led_state); struct vec payload; vec_init(&payload, 4096); struct rfb_server_fb_update_msg head = { .type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE, .n_rects = htons(1), }; struct rfb_server_fb_rect rect = { .encoding = htonl(RFB_ENCODING_QEMU_LED_STATE), }; vec_append(&payload, &head, sizeof(head)); vec_append(&payload, &rect, sizeof(rect)); if (have_qemu_led_state) { uint8_t data = client->pending_led_state; vec_append(&payload, &data, sizeof(data)); } else if (have_vmware_led_state) { uint32_t data = htonl(client->pending_led_state); vec_append(&payload, &data, sizeof(data)); } stream_send(client->net_stream, rcbuf_new(payload.data, payload.len), NULL, NULL); client->led_state = client->pending_led_state; return true; } EXPORT void nvnc_client_set_led_state(struct nvnc_client* client, enum nvnc_keyboard_led_state state) { client->pending_led_state = state; process_fb_update_requests(client); } EXPORT void nvnc_set_name(struct nvnc* self, const char* name) { strncpy(self->name, name, sizeof(self->name)); self->name[sizeof(self->name) - 1] = '\0'; } EXPORT bool nvnc_has_auth(void) { #ifdef ENABLE_TLS return true; #else return false; #endif } EXPORT int nvnc_set_tls_creds(struct nvnc* self, const char* privkey_path, const char* cert_path) { #ifdef ENABLE_TLS if (self->tls_creds) return -1; /* Note: This is globally reference counted, so we don't need to worry * about messing with other libraries. */ int rc = gnutls_global_init(); if (rc != GNUTLS_E_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "GnuTLS: Failed to initialise: %s", gnutls_strerror(rc)); return -1; } rc = gnutls_certificate_allocate_credentials(&self->tls_creds); if (rc != GNUTLS_E_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "GnuTLS: Failed to allocate credentials: %s", gnutls_strerror(rc)); goto cert_alloc_failure; } rc = gnutls_certificate_set_x509_key_file( self->tls_creds, cert_path, privkey_path, GNUTLS_X509_FMT_PEM); if (rc != GNUTLS_E_SUCCESS) { nvnc_log(NVNC_LOG_ERROR, "GnuTLS: Failed to load credentials: %s", gnutls_strerror(rc)); goto cert_set_failure; } return 0; cert_set_failure: gnutls_certificate_free_credentials(self->tls_creds); self->tls_creds = NULL; cert_alloc_failure: gnutls_global_deinit(); #endif return -1; } EXPORT int nvnc_enable_auth(struct nvnc* self, enum nvnc_auth_flags flags, nvnc_auth_fn auth_fn, void* userdata) { #ifdef HAVE_CRYPTO self->auth_flags = flags; self->auth_fn = auth_fn; self->auth_ud = userdata; return 0; #endif return -1; } EXPORT void nvnc_set_cursor(struct nvnc* self, struct nvnc_fb* fb, uint16_t width, uint16_t height, uint16_t hotspot_x, uint16_t hotspot_y, bool is_damaged) { if (self->cursor.buffer) { nvnc_fb_release(self->cursor.buffer); nvnc_fb_unref(self->cursor.buffer); } self->cursor.buffer = fb; if (fb) { // TODO: Hash cursors to check if they actually changed? nvnc_fb_ref(fb); nvnc_fb_hold(fb); self->cursor.width = width; self->cursor.height = height; self->cursor.hotspot_x = hotspot_x; self->cursor.hotspot_y = hotspot_y; } else { self->cursor.width = width; self->cursor.height = height; self->cursor.hotspot_x = 0; self->cursor.hotspot_y = 0; } if (!is_damaged) return; self->cursor_seq++; struct nvnc_client* client; LIST_FOREACH(client, &self->clients, link) process_fb_update_requests(client); } EXPORT int nvnc_set_rsa_creds(struct nvnc* self, const char* path) { #ifdef HAVE_CRYPTO crypto_rsa_priv_key_del(self->rsa_priv); crypto_rsa_pub_key_del(self->rsa_pub); self->rsa_priv = crypto_rsa_priv_key_new(); self->rsa_pub = crypto_rsa_pub_key_new(); bool ok = crypto_rsa_priv_key_load(self->rsa_priv, self->rsa_pub, path); return ok ? 0 : -1; #endif return -1; }