2019-09-07 16:51:07 +00:00
|
|
|
/*
|
2020-01-25 15:35:14 +00:00
|
|
|
* Copyright (c) 2019 - 2020 Andri Yngvason
|
2019-09-07 16:51:07 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
#include "rfb-proto.h"
|
2019-08-25 19:10:35 +00:00
|
|
|
#include "zrle.h"
|
2019-12-30 09:59:51 +00:00
|
|
|
#include "tight.h"
|
2019-09-19 18:14:24 +00:00
|
|
|
#include "raw-encoding.h"
|
2019-08-27 21:49:28 +00:00
|
|
|
#include "vec.h"
|
2019-08-31 23:16:55 +00:00
|
|
|
#include "type-macros.h"
|
2019-10-07 17:39:54 +00:00
|
|
|
#include "fb.h"
|
2019-08-28 22:46:47 +00:00
|
|
|
#include "neatvnc.h"
|
2019-12-30 17:22:19 +00:00
|
|
|
#include "common.h"
|
2019-12-30 20:13:40 +00:00
|
|
|
#include "pixels.h"
|
2020-01-25 15:35:14 +00:00
|
|
|
#include "stream.h"
|
2019-12-31 10:13:21 +00:00
|
|
|
#include "config.h"
|
2020-01-25 15:35:14 +00:00
|
|
|
#include "logging.h"
|
2020-04-01 22:53:22 +00:00
|
|
|
#include "usdt.h"
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/queue.h>
|
2020-01-19 19:00:18 +00:00
|
|
|
#include <sys/param.h>
|
2019-08-12 20:49:23 +00:00
|
|
|
#include <assert.h>
|
2020-03-16 20:09:22 +00:00
|
|
|
#include <aml.h>
|
2019-08-12 20:49:23 +00:00
|
|
|
#include <libdrm/drm_fourcc.h>
|
2019-08-25 19:10:35 +00:00
|
|
|
#include <pixman.h>
|
2019-09-15 21:51:07 +00:00
|
|
|
#include <pthread.h>
|
2020-03-16 20:09:22 +00:00
|
|
|
#include <errno.h>
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
#ifdef ENABLE_TLS
|
|
|
|
#include <gnutls/gnutls.h>
|
|
|
|
#endif
|
|
|
|
|
2019-09-10 18:17:45 +00:00
|
|
|
#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
|
|
|
|
|
2019-08-29 21:47:02 +00:00
|
|
|
#define DEFAULT_NAME "Neat VNC"
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-09-19 18:14:24 +00:00
|
|
|
#define EXPORT __attribute__((visibility("default")))
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-09-15 21:51:07 +00:00
|
|
|
struct fb_update_work {
|
2020-03-16 20:09:22 +00:00
|
|
|
struct aml_work* work;
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client;
|
2019-09-15 21:51:07 +00:00
|
|
|
struct pixman_region16 region;
|
|
|
|
struct rfb_pixel_format server_fmt;
|
|
|
|
struct vec frame;
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_fb* fb;
|
2019-09-15 21:51:07 +00:00
|
|
|
};
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
int schedule_client_update_fb(struct nvnc_client* client);
|
2019-10-07 20:12:59 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
static void client_close(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
log_debug("client_close(%p): ref %d\n", client, client->ref);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2019-08-31 23:30:08 +00:00
|
|
|
nvnc_client_fn fn = client->cleanup_fn;
|
|
|
|
if (fn)
|
|
|
|
fn(client);
|
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
LIST_REMOVE(client, link);
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_destroy(client->net_stream);
|
2020-04-03 00:18:54 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
2020-04-02 21:52:04 +00:00
|
|
|
tight_encoder_destroy(&client->tight_encoder);
|
2020-04-03 00:18:54 +00:00
|
|
|
#endif
|
2020-01-25 15:35:14 +00:00
|
|
|
deflateEnd(&client->z_stream);
|
2019-10-07 20:12:59 +00:00
|
|
|
pixman_region_fini(&client->damage);
|
2019-08-12 20:49:23 +00:00
|
|
|
free(client);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static inline void client_unref(struct nvnc_client* client)
|
2019-09-18 21:28:48 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
assert(client->ref > 0);
|
|
|
|
|
2019-09-18 21:28:48 +00:00
|
|
|
if (--client->ref == 0)
|
|
|
|
client_close(client);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static inline void client_ref(struct nvnc_client* client)
|
2019-09-18 21:28:48 +00:00
|
|
|
{
|
|
|
|
++client->ref;
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
static void close_after_write(void* userdata, enum stream_req_status status)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* client = userdata;
|
|
|
|
log_debug("close_after_write(%p): ref %d\n", client, client->ref);
|
|
|
|
stream_close(client->net_stream);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int handle_unsupported_version(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
char buffer[256];
|
|
|
|
|
|
|
|
client->state = VNC_CLIENT_STATE_ERROR;
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_error_reason* reason = (struct rfb_error_reason*)(buffer + 1);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
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));
|
|
|
|
(void)strcmp(reason->message, reason_string);
|
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
size_t len = 1 + sizeof(*reason) + strlen(reason_string);
|
|
|
|
stream_write(client->net_stream, buffer, len, close_after_write,
|
|
|
|
client);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_version_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc* server = client->server;
|
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
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);
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
struct rfb_security_types_msg security = { 0 };
|
|
|
|
security.n = 1;
|
|
|
|
security.types[0] = RFB_SECURITY_TYPE_NONE;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
#ifdef ENABLE_TLS
|
|
|
|
if (server->auth_fn)
|
|
|
|
security.types[0] = RFB_SECURITY_TYPE_VENCRYPT;
|
|
|
|
#endif
|
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
stream_write(client->net_stream, &security, sizeof(security), NULL,
|
|
|
|
NULL);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_SECURITY;
|
|
|
|
return 12;
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:58:43 +00:00
|
|
|
static int security_handshake_failed(struct nvnc_client* client,
|
|
|
|
const char* reason_string)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
char buffer[256];
|
|
|
|
|
|
|
|
client->state = VNC_CLIENT_STATE_ERROR;
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
uint8_t* result = (uint8_t*)buffer;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_error_reason* reason =
|
|
|
|
(struct rfb_error_reason*)(buffer + sizeof(*result));
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
*result = htonl(RFB_SECURITY_HANDSHAKE_FAILED);
|
|
|
|
reason->length = htonl(strlen(reason_string));
|
|
|
|
(void)strcmp(reason->message, reason_string);
|
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
size_t len = sizeof(*result) + sizeof(*reason) + strlen(reason_string);
|
|
|
|
stream_write(client->net_stream, buffer, len, close_after_write,
|
|
|
|
client);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
static int security_handshake_ok(struct nvnc_client* client)
|
|
|
|
{
|
|
|
|
uint32_t result = htonl(RFB_SECURITY_HANDSHAKE_OK);
|
2020-01-25 15:52:44 +00:00
|
|
|
return stream_write(client->net_stream, &result, sizeof(result), NULL,
|
|
|
|
NULL);
|
2020-01-25 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int send_byte(struct nvnc_client* client, uint8_t value)
|
|
|
|
{
|
2020-01-25 15:52:44 +00:00
|
|
|
return stream_write(client->net_stream, &value, 1, NULL, NULL);
|
2020-01-25 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-25 16:04:04 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
#ifdef ENABLE_TLS
|
|
|
|
static int vencrypt_send_version(struct nvnc_client* client)
|
|
|
|
{
|
|
|
|
struct rfb_vencrypt_version_msg msg = {
|
|
|
|
.major = 0,
|
|
|
|
.minor = 2,
|
|
|
|
};
|
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
return stream_write(client->net_stream, &msg, sizeof(msg), NULL, NULL);
|
2020-01-25 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-01-25 15:58:43 +00:00
|
|
|
security_handshake_failed(client, "Unsupported VeNCrypt version");
|
2020-01-25 15:35:14 +00:00
|
|
|
return sizeof(*msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
send_byte(client, 0);
|
|
|
|
|
|
|
|
struct rfb_vencrypt_subtypes_msg result = { .n = 1, };
|
|
|
|
result.types[0] = htonl(RFB_VENCRYPT_X509_PLAIN);
|
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
stream_write(client->net_stream, &result, sizeof(result), NULL, NULL);
|
2020-01-25 15:35:14 +00:00
|
|
|
|
|
|
|
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) {
|
2020-01-25 16:04:04 +00:00
|
|
|
client->state = VNC_CLIENT_STATE_ERROR;
|
|
|
|
send_byte_and_close(client, 0);
|
2020-01-25 15:35:14 +00:00
|
|
|
return sizeof(*msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
send_byte(client, 1);
|
|
|
|
|
|
|
|
if (stream_upgrade_to_tls(client->net_stream, client->server->tls_creds) < 0) {
|
2020-01-25 16:04:04 +00:00
|
|
|
client->state = VNC_CLIENT_STATE_ERROR;
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_close(client->net_stream);
|
|
|
|
client_unref(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)) {
|
|
|
|
log_debug("User \"%s\" authenticated\n", username);
|
|
|
|
security_handshake_ok(client);
|
|
|
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT;
|
|
|
|
} else {
|
|
|
|
log_debug("User \"%s\" rejected\n", username);
|
2020-01-25 15:58:43 +00:00
|
|
|
security_handshake_failed(client, "Invalid username or password");
|
2020-01-25 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return sizeof(*msg) + ulen + plen;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_security_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
if (client->buffer_len - client->buffer_index < 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
uint8_t type = client->msg_buffer[client->buffer_index];
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
switch (type) {
|
|
|
|
case RFB_SECURITY_TYPE_NONE:
|
|
|
|
security_handshake_ok(client);
|
|
|
|
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
|
|
|
|
default:
|
2020-01-25 15:58:43 +00:00
|
|
|
security_handshake_failed(client, "Unsupported security type");
|
2020-01-25 15:35:14 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
return sizeof(type);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static void disconnect_all_other_clients(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* node;
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* tmp;
|
|
|
|
|
|
|
|
LIST_FOREACH_SAFE (node, &client->server->clients, link, tmp)
|
|
|
|
if (node != client) {
|
|
|
|
log_debug("disconnect other client %p (ref %d)\n",
|
|
|
|
node, node->ref);
|
|
|
|
stream_close(node->net_stream);
|
|
|
|
client_unref(node);
|
|
|
|
}
|
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static void send_server_init_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* server = client->server;
|
|
|
|
struct vnc_display* display = &server->display;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
size_t name_len = strlen(display->name);
|
|
|
|
size_t size = sizeof(struct rfb_server_init_msg) + name_len;
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_server_init_msg* msg = calloc(1, size);
|
2019-08-12 20:49:23 +00:00
|
|
|
if (!msg) {
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_close(client->net_stream);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg->width = htons(display->width),
|
2019-10-20 22:13:51 +00:00
|
|
|
msg->height = htons(display->height), msg->name_length = htonl(name_len),
|
2019-08-12 20:49:23 +00:00
|
|
|
memcpy(msg->name_string, display->name, name_len);
|
2019-08-25 19:10:35 +00:00
|
|
|
|
2019-09-08 16:48:05 +00:00
|
|
|
int rc = rfb_pixfmt_from_fourcc(&msg->pixel_format, display->pixfmt);
|
|
|
|
if (rc < 0) {
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_close(client->net_stream);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-09-08 16:48:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-08-25 19:10:35 +00:00
|
|
|
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);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
struct rcbuf* payload = rcbuf_new(msg, size);
|
2020-01-25 15:52:44 +00:00
|
|
|
stream_send(client->net_stream, payload, NULL, NULL);
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_init_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
if (client->buffer_len - client->buffer_index < 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
uint8_t shared_flag = client->msg_buffer[client->buffer_index];
|
2019-09-06 20:07:09 +00:00
|
|
|
if (!shared_flag)
|
2019-08-12 20:49:23 +00:00
|
|
|
disconnect_all_other_clients(client);
|
|
|
|
|
|
|
|
send_server_init_message(client);
|
|
|
|
|
2019-08-31 23:30:08 +00:00
|
|
|
nvnc_client_fn fn = client->server->new_client_fn;
|
|
|
|
if (fn)
|
|
|
|
fn(client);
|
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
client->state = VNC_CLIENT_STATE_READY;
|
|
|
|
return sizeof(shared_flag);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_set_pixel_format(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
if (client->buffer_len - client->buffer_index <
|
|
|
|
4 + sizeof(struct rfb_pixel_format))
|
2019-08-12 20:49:23 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_pixel_format* fmt =
|
|
|
|
(struct rfb_pixel_format*)(client->msg_buffer +
|
|
|
|
client->buffer_index + 4);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
if (!fmt->true_colour_flag) {
|
|
|
|
/* We don't really know what to do with color maps right now */
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_close(client->net_stream);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-08-25 19:10:35 +00:00
|
|
|
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));
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-24 22:28:37 +00:00
|
|
|
client->has_pixfmt = true;
|
2019-08-25 19:10:35 +00:00
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
return 4 + sizeof(struct rfb_pixel_format);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_set_encodings(struct nvnc_client* client)
|
2019-08-12 23:33:06 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_client_set_encodings_msg* msg =
|
|
|
|
(struct rfb_client_set_encodings_msg*)(client->msg_buffer +
|
|
|
|
client->buffer_index);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2020-01-19 19:16:27 +00:00
|
|
|
size_t n_encodings = MIN(MAX_ENCODINGS, ntohs(msg->n_encodings));
|
|
|
|
size_t n = 0;
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-12-23 09:33:29 +00:00
|
|
|
if (client->buffer_len - client->buffer_index <
|
|
|
|
sizeof(*msg) + n_encodings * 4)
|
|
|
|
return 0;
|
|
|
|
|
2020-01-19 19:16:27 +00:00
|
|
|
for (size_t i = 0; i < n_encodings; ++i) {
|
2019-10-08 18:12:57 +00:00
|
|
|
enum rfb_encodings encoding = htonl(msg->encodings[i]);
|
|
|
|
|
|
|
|
switch (encoding) {
|
2019-09-19 18:14:24 +00:00
|
|
|
case RFB_ENCODING_RAW:
|
|
|
|
case RFB_ENCODING_COPYRECT:
|
|
|
|
case RFB_ENCODING_RRE:
|
|
|
|
case RFB_ENCODING_HEXTILE:
|
2019-12-30 09:59:51 +00:00
|
|
|
case RFB_ENCODING_TIGHT:
|
2019-09-19 18:14:24 +00:00
|
|
|
case RFB_ENCODING_TRLE:
|
|
|
|
case RFB_ENCODING_ZRLE:
|
|
|
|
case RFB_ENCODING_CURSOR:
|
|
|
|
case RFB_ENCODING_DESKTOPSIZE:
|
2020-04-03 19:56:44 +00:00
|
|
|
case RFB_ENCODING_JPEG_HIGHQ:
|
|
|
|
case RFB_ENCODING_JPEG_LOWQ:
|
2019-10-08 18:12:57 +00:00
|
|
|
client->encodings[n++] = encoding;
|
2019-08-12 23:33:06 +00:00
|
|
|
}
|
2019-10-08 18:12:57 +00:00
|
|
|
}
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-09-19 18:14:24 +00:00
|
|
|
client->n_encodings = n;
|
2019-08-12 23:33:06 +00:00
|
|
|
|
|
|
|
return sizeof(*msg) + 4 * n_encodings;
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static void process_fb_update_requests(struct nvnc_client* client)
|
2019-10-07 20:12:59 +00:00
|
|
|
{
|
|
|
|
if (!client->server->frame)
|
|
|
|
return;
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
if (client->net_stream->state == STREAM_STATE_CLOSED)
|
2019-10-07 20:12:59 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!pixman_region_not_empty(&client->damage))
|
|
|
|
return;
|
|
|
|
|
2019-10-12 17:29:08 +00:00
|
|
|
if (client->is_updating || client->n_pending_requests == 0)
|
2019-10-07 20:12:59 +00:00
|
|
|
return;
|
|
|
|
|
2020-04-04 12:33:18 +00:00
|
|
|
if (!nvnc_fb_lock(client->server->frame))
|
|
|
|
return;
|
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
client->is_updating = true;
|
|
|
|
|
2020-04-04 12:33:18 +00:00
|
|
|
if (schedule_client_update_fb(client) < 0)
|
|
|
|
nvnc_fb_unlock(client->server->frame);
|
2019-10-07 20:12:59 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_fb_update_request(struct nvnc_client* client)
|
2019-08-12 23:33:06 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* server = client->server;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_client_fb_update_req_msg* msg =
|
|
|
|
(struct rfb_client_fb_update_req_msg*)(client->msg_buffer +
|
|
|
|
client->buffer_index);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-12-23 09:33:29 +00:00
|
|
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
|
|
|
return 0;
|
|
|
|
|
2019-08-12 23:33:06 +00:00
|
|
|
int incremental = msg->incremental;
|
|
|
|
int x = ntohs(msg->x);
|
|
|
|
int y = ntohs(msg->y);
|
|
|
|
int width = ntohs(msg->width);
|
|
|
|
int height = ntohs(msg->height);
|
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
client->n_pending_requests++;
|
|
|
|
|
|
|
|
/* Note: The region sent from the client is ignored for incremental
|
|
|
|
* updates. This avoids superfluous complexity.
|
|
|
|
*/
|
|
|
|
if (!incremental)
|
2019-10-20 22:13:51 +00:00
|
|
|
pixman_region_union_rect(&client->damage, &client->damage, x, y,
|
|
|
|
width, height);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2020-04-01 22:53:22 +00:00
|
|
|
DTRACE_PROBE1(neatvnc, update_fb_request, client);
|
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
nvnc_fb_req_fn fn = server->fb_req_fn;
|
|
|
|
if (fn)
|
2019-08-31 23:16:55 +00:00
|
|
|
fn(client, incremental, x, y, width, height);
|
2019-08-27 21:49:28 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
process_fb_update_requests(client);
|
|
|
|
|
2019-08-12 23:33:06 +00:00
|
|
|
return sizeof(*msg);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_key_event(struct nvnc_client* client)
|
2019-08-12 23:33:06 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* server = client->server;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_client_key_event_msg* msg =
|
|
|
|
(struct rfb_client_key_event_msg*)(client->msg_buffer +
|
|
|
|
client->buffer_index);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-12-23 09:33:29 +00:00
|
|
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
|
|
|
return 0;
|
|
|
|
|
2019-08-12 23:33:06 +00:00
|
|
|
int down_flag = msg->down_flag;
|
2019-08-28 22:46:47 +00:00
|
|
|
uint32_t keysym = ntohl(msg->key);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
nvnc_key_fn fn = server->key_fn;
|
|
|
|
if (fn)
|
2019-08-31 23:16:55 +00:00
|
|
|
fn(client, keysym, !!down_flag);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
|
|
|
return sizeof(*msg);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_pointer_event(struct nvnc_client* client)
|
2019-08-12 23:33:06 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* server = client->server;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_client_pointer_event_msg* msg =
|
|
|
|
(struct rfb_client_pointer_event_msg*)(client->msg_buffer +
|
|
|
|
client->buffer_index);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
2019-12-23 09:33:29 +00:00
|
|
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
|
|
|
return 0;
|
|
|
|
|
2019-08-12 23:33:06 +00:00
|
|
|
int button_mask = msg->button_mask;
|
|
|
|
uint16_t x = ntohs(msg->x);
|
|
|
|
uint16_t y = ntohs(msg->y);
|
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
nvnc_pointer_fn fn = server->pointer_fn;
|
|
|
|
if (fn)
|
2019-08-31 23:16:55 +00:00
|
|
|
fn(client, x, y, button_mask);
|
2019-08-12 23:33:06 +00:00
|
|
|
|
|
|
|
return sizeof(*msg);
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_cut_text(struct nvnc_client* client)
|
2019-08-12 23:39:37 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct rfb_client_cut_text_msg* msg =
|
|
|
|
(struct rfb_client_cut_text_msg*)(client->msg_buffer +
|
|
|
|
client->buffer_index);
|
2019-08-12 23:39:37 +00:00
|
|
|
|
2019-12-23 09:33:29 +00:00
|
|
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
|
|
|
return 0;
|
|
|
|
|
2019-08-12 23:39:37 +00:00
|
|
|
uint32_t length = ntohl(msg->length);
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
|
|
|
|
return sizeof(*msg) + length;
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int on_client_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
if (client->buffer_len - client->buffer_index < 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
enum rfb_client_to_server_msg_type type =
|
2019-10-20 22:13:51 +00:00
|
|
|
client->msg_buffer[client->buffer_index];
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case RFB_CLIENT_TO_SERVER_SET_PIXEL_FORMAT:
|
2019-08-12 22:11:41 +00:00
|
|
|
return on_client_set_pixel_format(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
case RFB_CLIENT_TO_SERVER_SET_ENCODINGS:
|
2019-08-12 23:33:06 +00:00
|
|
|
return on_client_set_encodings(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
case RFB_CLIENT_TO_SERVER_FRAMEBUFFER_UPDATE_REQUEST:
|
2019-08-12 23:33:06 +00:00
|
|
|
return on_client_fb_update_request(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
case RFB_CLIENT_TO_SERVER_KEY_EVENT:
|
2019-08-12 23:33:06 +00:00
|
|
|
return on_client_key_event(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
case RFB_CLIENT_TO_SERVER_POINTER_EVENT:
|
2019-08-12 23:33:06 +00:00
|
|
|
return on_client_pointer_event(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
case RFB_CLIENT_TO_SERVER_CLIENT_CUT_TEXT:
|
2019-08-12 23:39:37 +00:00
|
|
|
return on_client_cut_text(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
log_debug("Got uninterpretable message from client: %p (ref %d)\n",
|
|
|
|
client, client->ref);
|
|
|
|
stream_close(client->net_stream);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
static int try_read_client_message(struct nvnc_client* client)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
|
|
|
switch (client->state) {
|
|
|
|
case VNC_CLIENT_STATE_ERROR:
|
2020-01-25 15:35:14 +00:00
|
|
|
return client->buffer_len - client->buffer_index;
|
2019-08-12 20:49:23 +00:00
|
|
|
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);
|
2020-01-25 15:35:14 +00:00
|
|
|
#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
|
2019-08-12 20:49:23 +00:00
|
|
|
case VNC_CLIENT_STATE_READY:
|
|
|
|
return on_client_message(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
abort();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
static void on_client_event(struct stream* stream, enum stream_event event)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* client = stream->userdata;
|
|
|
|
|
|
|
|
assert(client->net_stream == stream);
|
|
|
|
|
|
|
|
if (event == STREAM_EVENT_REMOTE_CLOSED) {
|
|
|
|
log_debug("Client %p (%d) hung up\n", client, client->ref);
|
|
|
|
stream_close(stream);
|
|
|
|
client_unref(client);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-25 16:41:23 +00:00
|
|
|
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);
|
2019-09-18 21:28:48 +00:00
|
|
|
|
2019-12-27 13:57:24 +00:00
|
|
|
if (n_read == 0)
|
2020-01-25 16:41:23 +00:00
|
|
|
return;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2019-12-27 13:57:24 +00:00
|
|
|
if (n_read < 0) {
|
2020-01-25 15:35:14 +00:00
|
|
|
if (errno != EAGAIN) {
|
|
|
|
log_debug("Client connection error: %p (ref %d)\n",
|
|
|
|
client, client->ref);
|
|
|
|
stream_close(stream);
|
|
|
|
client_unref(client);
|
|
|
|
}
|
|
|
|
|
2020-01-25 16:41:23 +00:00
|
|
|
return;
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client->buffer_len += n_read;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
int rc = try_read_client_message(client);
|
|
|
|
if (rc == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
client->buffer_index += rc;
|
2020-01-25 15:35:14 +00:00
|
|
|
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-12 22:11:41 +00:00
|
|
|
assert(client->buffer_index <= client->buffer_len);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
memmove(client->msg_buffer, client->msg_buffer + client->buffer_index,
|
2019-10-20 22:13:51 +00:00
|
|
|
client->buffer_index);
|
2019-08-12 20:49:23 +00:00
|
|
|
client->buffer_len -= client->buffer_index;
|
|
|
|
client->buffer_index = 0;
|
|
|
|
}
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
static void on_connection(void* obj)
|
2019-08-12 20:49:23 +00:00
|
|
|
{
|
2020-03-16 20:09:22 +00:00
|
|
|
struct nvnc* server = aml_get_userdata(obj);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client = calloc(1, sizeof(*client));
|
2019-08-12 20:49:23 +00:00
|
|
|
if (!client)
|
|
|
|
return;
|
|
|
|
|
2019-09-18 21:28:48 +00:00
|
|
|
client->ref = 1;
|
2019-08-12 20:49:23 +00:00
|
|
|
client->server = server;
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
int fd = accept(server->fd, NULL, 0);
|
|
|
|
if (fd < 0)
|
|
|
|
goto accept_failure;
|
|
|
|
|
|
|
|
client->net_stream = stream_new(fd, on_client_event, client);
|
|
|
|
if (!client->net_stream)
|
|
|
|
goto stream_failure;
|
|
|
|
|
2019-10-12 16:42:59 +00:00
|
|
|
int rc = deflateInit2(&client->z_stream,
|
2019-10-20 22:13:51 +00:00
|
|
|
/* compression level: */ 1,
|
|
|
|
/* method: */ Z_DEFLATED,
|
|
|
|
/* window bits: */ 15,
|
|
|
|
/* mem level: */ 9,
|
|
|
|
/* strategy: */ Z_DEFAULT_STRATEGY);
|
2019-10-12 16:42:59 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
if (rc != Z_OK)
|
|
|
|
goto deflate_failure;
|
2019-09-01 18:40:11 +00:00
|
|
|
|
2020-04-03 00:18:54 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
2020-04-02 21:52:04 +00:00
|
|
|
if (tight_encoder_init(&client->tight_encoder) < 0)
|
|
|
|
goto tight_failure;
|
2020-04-03 00:18:54 +00:00
|
|
|
#endif
|
2020-04-02 21:52:04 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
pixman_region_init(&client->damage);
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
struct rcbuf* payload = rcbuf_from_string(RFB_VERSION_MESSAGE);
|
|
|
|
if (!payload)
|
|
|
|
goto payload_failure;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:52:44 +00:00
|
|
|
stream_send(client->net_stream, payload, NULL, NULL);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
|
|
|
LIST_INSERT_HEAD(&server->clients, client, link);
|
|
|
|
|
|
|
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_VERSION;
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
log_debug("New client connection: %p (ref %d)\n", client, client->ref);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
return;
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
payload_failure:
|
2020-04-03 00:18:54 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
2020-04-02 21:52:04 +00:00
|
|
|
tight_encoder_destroy(&client->tight_encoder);
|
2020-04-03 00:18:54 +00:00
|
|
|
#endif
|
2020-04-02 21:52:04 +00:00
|
|
|
pixman_region_fini(&client->damage);
|
2020-04-03 00:18:54 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
2020-04-02 21:52:04 +00:00
|
|
|
tight_failure:
|
2020-04-03 00:18:54 +00:00
|
|
|
#endif
|
2020-01-25 15:35:14 +00:00
|
|
|
deflateEnd(&client->z_stream);
|
|
|
|
deflate_failure:
|
|
|
|
stream_destroy(client->net_stream);
|
|
|
|
stream_failure:
|
|
|
|
close(fd);
|
|
|
|
accept_failure:
|
|
|
|
free(client);
|
2019-08-12 20:49:23 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
log_debug("Failed to accept a connection\n");
|
2019-08-12 20:49:23 +00:00
|
|
|
}
|
2019-08-12 22:11:41 +00:00
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* nvnc_open(const char* address, uint16_t port)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
2020-03-21 17:27:30 +00:00
|
|
|
aml_require_workers(aml_get_default(), -1);
|
2020-03-16 20:09:22 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* self = calloc(1, sizeof(*self));
|
2019-08-28 22:46:47 +00:00
|
|
|
if (!self)
|
|
|
|
return NULL;
|
|
|
|
|
2019-08-29 21:47:02 +00:00
|
|
|
strcpy(self->display.name, DEFAULT_NAME);
|
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
LIST_INIT(&self->clients);
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
self->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
if (self->fd < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
int one = 1;
|
|
|
|
if (setsockopt(self->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0)
|
|
|
|
goto failure;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct sockaddr_in addr = {0};
|
2019-08-28 22:46:47 +00:00
|
|
|
addr.sin_family = AF_INET;
|
|
|
|
addr.sin_addr.s_addr = inet_addr(address);
|
|
|
|
addr.sin_port = htons(port);
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
if (bind(self->fd, (const struct sockaddr*)&addr, sizeof(addr)) < 0)
|
2019-08-28 22:46:47 +00:00
|
|
|
goto failure;
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
if (listen(self->fd, 16) < 0)
|
2019-08-28 22:46:47 +00:00
|
|
|
goto failure;
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
self->poll_handle = aml_handler_new(self->fd, on_connection, self, NULL);
|
|
|
|
if (!self->poll_handle)
|
|
|
|
goto failure;
|
|
|
|
|
|
|
|
if (aml_start(aml_get_default(), self->poll_handle) < 0)
|
|
|
|
goto start_failure;
|
2020-01-25 15:35:14 +00:00
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
return self;
|
2020-01-25 15:35:14 +00:00
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
start_failure:
|
|
|
|
aml_unref(self->poll_handle);
|
2019-08-28 22:46:47 +00:00
|
|
|
failure:
|
2020-01-25 15:35:14 +00:00
|
|
|
close(self->fd);
|
2019-08-28 22:46:47 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_close(struct nvnc* self)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
if (self->frame)
|
|
|
|
nvnc_fb_unref(self->frame);
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* tmp;
|
|
|
|
LIST_FOREACH_SAFE (client, &self->clients, link, tmp)
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
aml_stop(aml_get_default(), self->poll_handle);
|
2020-01-25 15:35:14 +00:00
|
|
|
close(self->fd);
|
|
|
|
|
|
|
|
#ifdef ENABLE_TLS
|
|
|
|
if (self->tls_creds) {
|
|
|
|
gnutls_certificate_free_credentials(self->tls_creds);
|
|
|
|
gnutls_global_deinit();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
aml_unref(self->poll_handle);
|
2019-08-28 22:46:47 +00:00
|
|
|
free(self);
|
|
|
|
}
|
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
static void on_write_frame_done(void* userdata, enum stream_req_status status)
|
2019-09-06 19:26:18 +00:00
|
|
|
{
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* client = userdata;
|
2019-12-23 12:10:58 +00:00
|
|
|
client->is_updating = false;
|
2020-01-25 15:35:14 +00:00
|
|
|
client_unref(client);
|
2019-09-06 19:26:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
enum rfb_encodings choose_frame_encoding(struct nvnc_client* client)
|
2019-09-19 18:14:24 +00:00
|
|
|
{
|
2020-01-19 19:16:27 +00:00
|
|
|
for (size_t i = 0; i < client->n_encodings; ++i)
|
2019-09-19 18:14:24 +00:00
|
|
|
switch (client->encodings[i]) {
|
|
|
|
case RFB_ENCODING_RAW:
|
2019-12-30 09:59:51 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
|
|
|
case RFB_ENCODING_TIGHT:
|
|
|
|
#endif
|
2019-09-19 18:14:24 +00:00
|
|
|
case RFB_ENCODING_ZRLE:
|
|
|
|
return client->encodings[i];
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
void do_client_update_fb(void* work)
|
2019-09-15 21:51:07 +00:00
|
|
|
{
|
2020-03-16 20:09:22 +00:00
|
|
|
struct fb_update_work* update = aml_get_userdata(work);
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client = update->client;
|
2020-04-04 12:33:18 +00:00
|
|
|
struct nvnc_fb* fb = update->fb;
|
2019-09-15 21:51:07 +00:00
|
|
|
|
2019-09-19 18:14:24 +00:00
|
|
|
enum rfb_encodings encoding = choose_frame_encoding(client);
|
2020-01-19 21:27:17 +00:00
|
|
|
if (encoding == -1) {
|
2020-01-25 15:35:14 +00:00
|
|
|
stream_close(client->net_stream);
|
2020-01-19 21:27:17 +00:00
|
|
|
client_unref(client);
|
|
|
|
return;
|
|
|
|
}
|
2019-09-19 18:14:24 +00:00
|
|
|
|
2020-01-24 22:28:37 +00:00
|
|
|
if (!client->has_pixfmt) {
|
2020-01-19 22:54:52 +00:00
|
|
|
rfb_pixfmt_from_fourcc(&client->pixfmt, fb->fourcc_format);
|
2020-01-24 22:28:37 +00:00
|
|
|
client->has_pixfmt = true;
|
2020-01-19 22:54:52 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 18:14:24 +00:00
|
|
|
switch (encoding) {
|
|
|
|
case RFB_ENCODING_RAW:
|
|
|
|
raw_encode_frame(&update->frame, &client->pixfmt, fb,
|
2019-10-20 22:13:51 +00:00
|
|
|
&update->server_fmt, &update->region);
|
2019-09-19 18:14:24 +00:00
|
|
|
break;
|
2019-12-30 09:59:51 +00:00
|
|
|
#ifdef ENABLE_TIGHT
|
|
|
|
case RFB_ENCODING_TIGHT:
|
2020-04-02 21:52:04 +00:00
|
|
|
tight_encode_frame(&client->tight_encoder, &update->frame, fb,
|
2020-04-03 00:18:54 +00:00
|
|
|
&update->server_fmt, &update->region);
|
2019-12-30 09:59:51 +00:00
|
|
|
break;
|
|
|
|
#endif
|
2019-09-19 18:14:24 +00:00
|
|
|
case RFB_ENCODING_ZRLE:
|
|
|
|
zrle_encode_frame(&client->z_stream, &update->frame,
|
2019-10-20 22:13:51 +00:00
|
|
|
&client->pixfmt, fb, &update->server_fmt,
|
|
|
|
&update->region);
|
2019-09-19 18:14:24 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2019-09-15 21:51:07 +00:00
|
|
|
}
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
void on_client_update_fb_done(void* work)
|
2019-09-15 21:51:07 +00:00
|
|
|
{
|
2020-03-16 20:09:22 +00:00
|
|
|
struct fb_update_work* update = aml_get_userdata(work);
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client = update->client;
|
|
|
|
struct vec* frame = &update->frame;
|
2019-09-15 21:51:07 +00:00
|
|
|
|
2020-04-04 12:33:18 +00:00
|
|
|
nvnc_fb_unlock(update->fb);
|
|
|
|
nvnc_fb_unref(update->fb);
|
2020-01-25 15:35:14 +00:00
|
|
|
|
2020-04-04 12:33:18 +00:00
|
|
|
client_ref(client);
|
2020-01-25 15:35:14 +00:00
|
|
|
|
|
|
|
if (client->net_stream->state != STREAM_STATE_CLOSED) {
|
|
|
|
struct rcbuf* payload = rcbuf_new(frame->data, frame->len);
|
2020-04-03 23:11:12 +00:00
|
|
|
DTRACE_PROBE1(neatvnc, send_fb_start, client);
|
2020-01-25 15:52:44 +00:00
|
|
|
stream_send(client->net_stream, payload, on_write_frame_done,
|
|
|
|
client);
|
2020-04-03 23:11:12 +00:00
|
|
|
DTRACE_PROBE1(neatvnc, send_fb_done, client);
|
2020-01-25 15:35:14 +00:00
|
|
|
} else {
|
2019-12-23 12:10:58 +00:00
|
|
|
client->is_updating = false;
|
2020-01-25 15:35:14 +00:00
|
|
|
vec_destroy(frame);
|
|
|
|
client_unref(client);
|
|
|
|
}
|
2019-09-15 21:51:07 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
client->n_pending_requests--;
|
|
|
|
process_fb_update_requests(client);
|
2019-12-27 15:07:48 +00:00
|
|
|
|
2020-04-01 22:53:22 +00:00
|
|
|
DTRACE_PROBE1(neatvnc, update_fb_done, client);
|
|
|
|
|
2019-12-27 15:07:48 +00:00
|
|
|
pixman_region_fini(&update->region);
|
2020-01-25 15:35:14 +00:00
|
|
|
|
|
|
|
client_unref(client);
|
2019-09-15 21:51:07 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
int schedule_client_update_fb(struct nvnc_client* client)
|
2019-09-15 21:51:07 +00:00
|
|
|
{
|
2019-10-07 20:12:59 +00:00
|
|
|
struct nvnc_fb* fb = client->server->frame;
|
|
|
|
assert(fb);
|
|
|
|
|
2020-04-01 22:53:22 +00:00
|
|
|
DTRACE_PROBE1(neatvnc, update_fb_start, client);
|
|
|
|
|
2019-10-20 22:13:51 +00:00
|
|
|
struct fb_update_work* work = calloc(1, sizeof(*work));
|
2019-09-15 21:51:07 +00:00
|
|
|
if (!work)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (rfb_pixfmt_from_fourcc(&work->server_fmt, fb->fourcc_format) < 0)
|
|
|
|
goto pixfmt_failure;
|
|
|
|
|
|
|
|
work->client = client;
|
|
|
|
work->fb = fb;
|
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
/* The client's damage is exchanged for an empty one */
|
|
|
|
work->region = client->damage;
|
|
|
|
pixman_region_init(&client->damage);
|
2019-09-15 21:51:07 +00:00
|
|
|
|
|
|
|
int rc = vec_init(&work->frame, fb->width * fb->height * 3 / 2);
|
|
|
|
if (rc < 0)
|
|
|
|
goto vec_failure;
|
|
|
|
|
2019-09-18 21:28:48 +00:00
|
|
|
client_ref(client);
|
2019-10-08 17:53:12 +00:00
|
|
|
nvnc_fb_ref(fb);
|
2019-09-18 21:28:48 +00:00
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
struct aml_work* obj =
|
|
|
|
aml_work_new(do_client_update_fb, on_client_update_fb_done,
|
|
|
|
work, free);
|
|
|
|
if (!obj) {
|
|
|
|
goto oom_failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = aml_start(aml_get_default(), obj);
|
|
|
|
aml_unref(obj);
|
2019-09-15 21:51:07 +00:00
|
|
|
if (rc < 0)
|
2020-03-16 20:09:22 +00:00
|
|
|
goto start_failure;
|
|
|
|
|
|
|
|
work->work = obj;
|
2019-09-15 21:51:07 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2020-03-16 20:09:22 +00:00
|
|
|
start_failure:
|
|
|
|
work = NULL; /* handled in unref */
|
|
|
|
oom_failure:
|
2019-10-08 17:53:12 +00:00
|
|
|
nvnc_fb_unref(fb);
|
2019-09-18 21:28:48 +00:00
|
|
|
client_unref(client);
|
2019-09-15 21:51:07 +00:00
|
|
|
vec_destroy(&work->frame);
|
|
|
|
vec_failure:
|
|
|
|
pixfmt_failure:
|
|
|
|
free(work);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-08-28 22:46:47 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
int nvnc_feed_frame(struct nvnc* self, struct nvnc_fb* fb,
|
|
|
|
const struct pixman_region16* damage)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_client* client;
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
if (self->frame)
|
|
|
|
nvnc_fb_unref(self->frame);
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
self->frame = fb;
|
|
|
|
nvnc_fb_ref(self->frame);
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2020-01-25 15:35:14 +00:00
|
|
|
struct nvnc_client* tmp;
|
|
|
|
LIST_FOREACH_SAFE (client, &self->clients, link, tmp) {
|
|
|
|
if (client->net_stream->state == STREAM_STATE_CLOSED)
|
2019-09-06 20:07:09 +00:00
|
|
|
continue;
|
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
pixman_region_union(&client->damage, &client->damage,
|
2019-10-20 22:13:51 +00:00
|
|
|
(struct pixman_region16*)damage);
|
2019-10-07 20:12:59 +00:00
|
|
|
pixman_region_intersect_rect(&client->damage, &client->damage,
|
2019-10-20 22:13:51 +00:00
|
|
|
0, 0, fb->width, fb->height);
|
2019-08-28 22:46:47 +00:00
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
process_fb_update_requests(client);
|
2019-08-28 22:46:47 +00:00
|
|
|
}
|
|
|
|
|
2019-10-07 20:12:59 +00:00
|
|
|
return 0;
|
2019-08-28 22:46:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_userdata(void* self, void* userdata)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc_common* common = self;
|
2019-08-31 23:16:55 +00:00
|
|
|
common->userdata = userdata;
|
2019-08-28 22:46:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void* nvnc_get_userdata(const void* self)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
2019-10-20 22:13:51 +00:00
|
|
|
const struct nvnc_common* common = self;
|
2019-08-31 23:16:55 +00:00
|
|
|
return common->userdata;
|
2019-08-28 22:46:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_key_fn(struct nvnc* self, nvnc_key_fn fn)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
|
|
|
self->key_fn = fn;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_pointer_fn(struct nvnc* self, nvnc_pointer_fn fn)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
|
|
|
self->pointer_fn = fn;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_fb_req_fn(struct nvnc* self, nvnc_fb_req_fn fn)
|
2019-08-28 22:46:47 +00:00
|
|
|
{
|
|
|
|
self->fb_req_fn = fn;
|
|
|
|
}
|
|
|
|
|
2019-08-31 23:30:08 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_new_client_fn(struct nvnc* self, nvnc_client_fn fn)
|
2019-08-31 23:30:08 +00:00
|
|
|
{
|
|
|
|
self->new_client_fn = fn;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_client_cleanup_fn(struct nvnc_client* self, nvnc_client_fn fn)
|
2019-08-31 23:30:08 +00:00
|
|
|
{
|
|
|
|
self->cleanup_fn = fn;
|
|
|
|
}
|
|
|
|
|
2019-08-29 21:47:02 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_dimensions(struct nvnc* self, uint16_t width, uint16_t height,
|
|
|
|
uint32_t fourcc_format)
|
2019-08-12 22:11:41 +00:00
|
|
|
{
|
2019-08-29 21:47:02 +00:00
|
|
|
self->display.width = width;
|
|
|
|
self->display.height = height;
|
|
|
|
self->display.pixfmt = fourcc_format;
|
|
|
|
}
|
2019-08-12 22:11:41 +00:00
|
|
|
|
2019-08-31 23:16:55 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
struct nvnc* nvnc_get_server(const struct nvnc_client* client)
|
2019-08-31 23:16:55 +00:00
|
|
|
{
|
|
|
|
return client->server;
|
|
|
|
}
|
|
|
|
|
2019-08-29 21:47:02 +00:00
|
|
|
EXPORT
|
2019-10-20 22:13:51 +00:00
|
|
|
void nvnc_set_name(struct nvnc* self, const char* name)
|
2019-08-29 21:47:02 +00:00
|
|
|
{
|
|
|
|
strncpy(self->display.name, name, sizeof(self->display.name));
|
|
|
|
self->display.name[sizeof(self->display.name) - 1] = '\0';
|
2019-08-12 22:11:41 +00:00
|
|
|
}
|
2020-01-25 15:35:14 +00:00
|
|
|
|
|
|
|
EXPORT
|
|
|
|
bool nvnc_has_auth(void)
|
|
|
|
{
|
|
|
|
#ifdef ENABLE_TLS
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT
|
|
|
|
int nvnc_enable_auth(struct nvnc* self, const char* privkey_path,
|
|
|
|
const char* cert_path, nvnc_auth_fn auth_fn,
|
|
|
|
void* userdata)
|
|
|
|
{
|
|
|
|
#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) {
|
|
|
|
log_error("GnuTLS: Failed to initialise: %s\n",
|
|
|
|
gnutls_strerror(rc));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = gnutls_certificate_allocate_credentials(&self->tls_creds);
|
|
|
|
if (rc != GNUTLS_E_SUCCESS) {
|
|
|
|
log_error("GnuTLS: Failed to allocate credentials: %s\n",
|
|
|
|
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) {
|
|
|
|
log_error("GnuTLS: Failed to load credentials: %s\n",
|
|
|
|
gnutls_strerror(rc));
|
|
|
|
goto cert_set_failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->auth_fn = auth_fn;
|
|
|
|
self->auth_ud = userdata;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|