/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * Copyright (c) 2015 Leon Dang * Copyright 2020 Joyent, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2022 OmniOS Community Edition (OmniOSce) Association. */ /* * References to the RFB protocol specification refer to: * - [1] https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NO_OPENSSL #include #endif #include #include #include #include #include #ifndef WITHOUT_CAPSICUM #include #include #include #endif #include "bhyvegc.h" #include "config.h" #include "debug.h" #include "console.h" #include "rfb.h" #include "rfb_impl.h" #include "sockstream.h" static uint_t rfb_debug = 0; static list_t rfb_list; static id_space_t *rfb_idspace; static bool rfb_sse42; static pthread_once_t rfb_once = PTHREAD_ONCE_INIT; extern int raw_stdio; static void rfb_send_extended_keyevent_update_msg(rfb_client_t *); static void rfb_printf(rfb_client_t *c, rfb_loglevel_t level, const char *fmt, ...) { FILE *fp = stdout; va_list ap; switch (level) { case RFB_LOGDEBUG: if (rfb_debug == 0) return; /* FALLTHROUGH */ case RFB_LOGERR: fp = stderr; /* FALLTHROUGH */ case RFB_LOGWARN: if (c != NULL) (void) fprintf(fp, "rfb%u: ", c->rc_instance); else (void) fprintf(fp, "rfb: "); va_start(ap, fmt); (void) vfprintf(fp, fmt, ap); va_end(ap); if (raw_stdio) (void) fprintf(fp, "\r\n"); else (void) fprintf(fp, "\n"); (void) fflush(fp); } } static void rfb_init_once(void) { uint_t cpu_registers[4], ecx; do_cpuid(1, cpu_registers); ecx = cpu_registers[2]; rfb_sse42 = (ecx & CPUID2_SSE42) != 0; if (rfb_sse42) rfb_printf(NULL, RFB_LOGDEBUG, "enabled fast crc32"); else rfb_printf(NULL, RFB_LOGWARN, "no support for fast crc32"); if (get_config_bool_default("rfb.debug", false)) rfb_debug = 1; list_create(&rfb_list, sizeof (rfb_server_t), offsetof(rfb_server_t, rs_node)); rfb_idspace = id_space_create("rfb", 0, INT32_MAX); } static void rfb_free_client(rfb_client_t *c) { free(c->rc_crc); free(c->rc_crc_tmp); free(c->rc_zbuf); free(c->rc_gci.data); if (c->rc_encodings & RFB_ENCODING_ZLIB) (void) deflateEnd(&c->rc_zstream); if (c->rc_fd != -1) (void) close(c->rc_fd); free(c); } /* * Calculate CRC32 using SSE4.2; Intel or AMD Bulldozer+ CPUs only */ static inline uint32_t fast_crc32(void *buf, int len, uint32_t crcval) { uint32_t q = len / sizeof (uint32_t); uint32_t *p = (uint32_t *)buf; while (q--) { /* BEGIN CSTYLED */ asm volatile ( /* crc32l %ecx,%esi */ ".byte 0xf2, 0xf, 0x38, 0xf1, 0xf1;" :"=S" (crcval) :"0" (crcval), "c" (*p) ); /* END CSTYLED */ p++; } return (crcval); } static void rfb_send_client_status(rfb_client_t *c, uint32_t status, const char *msg) { rfb_printf(c, RFB_LOGDEBUG, "sending client status %u (%s)", status, msg ? msg : "NULL"); status = htonl(status); (void) stream_write(c->rc_fd, &status, sizeof (status)); if (msg != NULL && status != 0 && c->rc_cver == RFB_CVER_3_8) { char buf[4]; rfb_printf(c, RFB_LOGWARN, msg); be32enc(buf, strlen((char *)msg)); (void) stream_write(c->rc_fd, buf, 4); (void) stream_write(c->rc_fd, msg, strlen((char *)msg)); } } static bool rfb_handshake_version(rfb_client_t *c) { unsigned char buf[RFB_VERSION_LEN]; ssize_t l; rfb_printf(c, RFB_LOGDEBUG, "handshake version"); if (stream_write(c->rc_fd, RFB_VERSION, RFB_VERSION_LEN) != RFB_VERSION_LEN) { rfb_printf(c, RFB_LOGWARN, "could not send server version."); return (false); } l = stream_read(c->rc_fd, buf, sizeof (buf)); if (l <= 0) { rfb_printf(c, RFB_LOGWARN, "client version not read"); return (false); } else if (l != RFB_VERSION_LEN) { rfb_printf(c, RFB_LOGWARN, "client sent short version - '%.*s'", l, buf); return (false); } rfb_printf(c, RFB_LOGDEBUG, "version handshake, client ver '%.*s'", l - 1, buf); if (strncmp(RFB_VERSION, (char *)buf, RFB_VERSION_LEN - 2) != 0) { rfb_printf(c, RFB_LOGERR, "bad client version '%.*s'", l, buf); return (false); } switch (buf[RFB_VERSION_LEN - 2]) { case '8': c->rc_cver = RFB_CVER_3_8; break; case '7': c->rc_cver = RFB_CVER_3_7; break; case '5': /* * From the RFB specification[1], section 7.1.1: * "version 3.5 was wrongly reported by some clients, but this * should be interpreted by all servers as 3.3." */ case '3': c->rc_cver = RFB_CVER_3_3; break; default: rfb_printf(c, RFB_LOGERR, "unsupported client version '%.*s'", l - 1, buf); return (false); } return (true); } static bool rfb_handshake_auth(rfb_client_t *c) { unsigned char buf[RFBP_SECURITY_VNC_AUTH_LEN]; int auth_type; rfb_printf(c, RFB_LOGDEBUG, "handshake auth"); auth_type = RFBP_SECURITY_NONE; #ifndef NO_OPENSSL if (c->rc_s->rs_password != NULL) auth_type = RFBP_SECURITY_VNC_AUTH; #endif switch (c->rc_cver) { case RFB_CVER_3_3: /* * RFB specification[1] section 7.1.2: * The server decides the security type and sends a single word. */ be32enc(buf, auth_type); (void) stream_write(c->rc_fd, buf, 4); break; case RFB_CVER_3_7: case RFB_CVER_3_8: /* Send list of supported types. */ buf[0] = 1; /* list length */ buf[1] = auth_type; (void) stream_write(c->rc_fd, buf, 2); /* Read agreed security type. */ if (stream_read(c->rc_fd, buf, 1) != 1) { rfb_printf(c, RFB_LOGWARN, "auth fail, no type from client"); return (false); } if (buf[0] != auth_type) { rfb_send_client_status(c, 1, "Auth failed: authentication type mismatch"); return (false); } break; } if (auth_type == RFBP_SECURITY_NONE) { /* * According to the RFB specification[1], section 7.2.1, for a * security type of 'None', client versions 3.3 and 3.7 expect * to move straight to the ClientInit phase, without the server * sending a response. For version 3.8, a SecurityResult word * needs to be sent indicating success. */ switch (c->rc_cver) { case RFB_CVER_3_3: case RFB_CVER_3_7: break; case RFB_CVER_3_8: rfb_send_client_status(c, 0, NULL); break; } return (true); } /* Perform VNC authentication. */ #ifdef NO_OPENSSL rfb_printf(c, RFB_LOGERR, "Auth not supported, no OpenSSL in your system"); rfb_send_client_status(c, 1, "Auth failed."); return (false); #else unsigned char challenge[RFBP_SECURITY_VNC_AUTH_LEN]; unsigned char keystr[RFBP_SECURITY_VNC_PASSWD_LEN]; unsigned char crypt_expected[RFBP_SECURITY_VNC_AUTH_LEN]; DES_key_schedule ks; /* * The client encrypts the challenge with DES, using a password * supplied by the user as the key. * To form the key, the password is truncated to eight characters, or * padded with null bytes on the right. * The client then sends the resulting 16-bytes response. */ (void) strncpy((char *)keystr, c->rc_s->rs_password, RFBP_SECURITY_VNC_PASSWD_LEN); /* * VNC clients encrypt the challenge with all the bit fields in each * byte of the password mirrored. * Here we flip each byte of the keystr. */ for (uint_t i = 0; i < RFBP_SECURITY_VNC_PASSWD_LEN; i++) { keystr[i] = (keystr[i] & 0xf0) >> 4 | (keystr[i] & 0x0f) << 4; keystr[i] = (keystr[i] & 0xcc) >> 2 | (keystr[i] & 0x33) << 2; keystr[i] = (keystr[i] & 0xaa) >> 1 | (keystr[i] & 0x55) << 1; } /* Initialize a 16-byte random challenge. */ arc4random_buf(challenge, sizeof (challenge)); /* Send the challenge to the client. */ if (stream_write(c->rc_fd, challenge, RFBP_SECURITY_VNC_AUTH_LEN) != RFBP_SECURITY_VNC_AUTH_LEN) { rfb_printf(c, RFB_LOGERR, "failed to send challenge to client"); return (false); } /* Receive the 16-byte challenge response. */ if (stream_read(c->rc_fd, buf, RFBP_SECURITY_VNC_AUTH_LEN) != RFBP_SECURITY_VNC_AUTH_LEN) { rfb_send_client_status(c, 1, "Challenge response read failed"); return (false); } memcpy(crypt_expected, challenge, RFBP_SECURITY_VNC_AUTH_LEN); /* Encrypt the Challenge with DES. */ DES_set_key_unchecked((const_DES_cblock *)keystr, &ks); DES_ecb_encrypt((const_DES_cblock *)challenge, (const_DES_cblock *)crypt_expected, &ks, DES_ENCRYPT); DES_ecb_encrypt( (const_DES_cblock *)(challenge + RFBP_SECURITY_VNC_PASSWD_LEN), (const_DES_cblock *)(crypt_expected + RFBP_SECURITY_VNC_PASSWD_LEN), &ks, DES_ENCRYPT); if (memcmp(crypt_expected, buf, RFBP_SECURITY_VNC_AUTH_LEN) != 0) { rfb_send_client_status(c, 1, "Auth failed: Invalid password."); return (false); } rfb_printf(c, RFB_LOGDEBUG, "authentication succeeded"); rfb_send_client_status(c, 0, NULL); #endif return (true); } static bool rfb_handshake_init_message(rfb_client_t *c) { struct bhyvegc_image *gci; char buf[1]; char *name; rfb_printf(c, RFB_LOGDEBUG, "handshake server init"); /* Read the client init message. */ if (stream_read(c->rc_fd, buf, 1) != 1) { rfb_printf(c, RFB_LOGWARN, "client did not send init"); return (false); } if (buf[0] == 0) { rfb_client_t *oc; rfb_printf(c, RFB_LOGDEBUG, "client requested exclusive access"); pthread_mutex_lock(&c->rc_s->rs_clientlock); c->rc_s->rs_exclusive = true; /* Disconnect all other clients. */ for (oc = list_head(&c->rc_s->rs_clients); oc != NULL; oc = list_next(&c->rc_s->rs_clients, oc)) { if (oc != c) oc->rc_closing = true; } pthread_mutex_unlock(&c->rc_s->rs_clientlock); } else { rfb_printf(c, RFB_LOGDEBUG, "client requested shared access"); pthread_mutex_lock(&c->rc_s->rs_clientlock); if (c->rc_s->rs_exclusive) { rfb_printf(c, RFB_LOGWARN, "deny due to existing exclusive session"); pthread_mutex_unlock(&c->rc_s->rs_clientlock); return (false); } pthread_mutex_unlock(&c->rc_s->rs_clientlock); } gci = console_get_image(); c->rc_sinfo.rsi_width = htons(gci->width); c->rc_sinfo.rsi_height = htons(gci->height); c->rc_width = gci->width; c->rc_height = gci->height; if (c->rc_s->rs_name != NULL) name = (char *)c->rc_s->rs_name; else name = "bhyve"; c->rc_sinfo.rsi_namelen = htonl(strlen(name)); (void) stream_write(c->rc_fd, &c->rc_sinfo, sizeof (c->rc_sinfo)); (void) stream_write(c->rc_fd, name, strlen(name)); return (true); } static bool rfb_handshake(rfb_client_t *c) { if (!rfb_handshake_version(c)) return (false); if (!rfb_handshake_auth(c)) return (false); if (!rfb_handshake_init_message(c)) return (false); return (true); } static void rfb_print_pixfmt(rfb_client_t *c, rfb_pixfmt_t *px, rfb_loglevel_t level) { rfb_printf(c, level, "%20s: %u", "bpp", px->rp_bpp); rfb_printf(c, level, "%20s: %u", "depth", px->rp_depth); rfb_printf(c, level, "%20s: %u", "bigendian", px->rp_bigendian); rfb_printf(c, level, "%20s: %u", "truecolour", px->rp_truecolour); rfb_printf(c, level, "%20s: %u", "r_max", ntohs(px->rp_r_max)); rfb_printf(c, level, "%20s: %u", "g_max", ntohs(px->rp_g_max)); rfb_printf(c, level, "%20s: %u", "b_max", ntohs(px->rp_b_max)); rfb_printf(c, level, "%20s: %u", "r_shift", px->rp_r_shift); rfb_printf(c, level, "%20s: %u", "g_shift", px->rp_g_shift); rfb_printf(c, level, "%20s: %u", "b_shift", px->rp_b_shift); } static bool rfb_recv_set_pixel_format(rfb_client_t *c) { rfb_cs_pixfmt_msg_t msg; rfb_pixfmt_t *newpx = &msg.rp_pixfmt; rfb_pixfmt_t *oldpx = &c->rc_sinfo.rsi_pixfmt; rfb_pixfmt_t *spx = &c->rc_s->rs_pixfmt; rfb_printf(c, RFB_LOGDEBUG, "received pixel format"); if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); /* * The client has sent its desired pixel format. The protocol does not * have a mechanism to reject this, we are supposed to just start using * the requested format from the next update. * * At present, we can only support alternative rgb-shift values and * will accept (and ignore) a new depth value. */ if (oldpx->rp_bpp != newpx->rp_bpp || oldpx->rp_bigendian != newpx->rp_bigendian || oldpx->rp_truecolour != newpx->rp_truecolour || oldpx->rp_r_max != newpx->rp_r_max || oldpx->rp_g_max != newpx->rp_g_max || oldpx->rp_b_max != newpx->rp_b_max) { rfb_printf(c, RFB_LOGWARN, "unsupported pixfmt from client"); rfb_print_pixfmt(c, newpx, RFB_LOGWARN); return (false); } rfb_print_pixfmt(c, newpx, RFB_LOGDEBUG); /* Check if the new shifts match the server's native values. */ if (newpx->rp_r_shift != spx->rp_r_shift || newpx->rp_g_shift != spx->rp_g_shift || newpx->rp_b_shift != spx->rp_b_shift) { c->rc_custom_pixfmt = true; rfb_printf(c, RFB_LOGDEBUG, "Using custom pixfmt"); } else { c->rc_custom_pixfmt = false; rfb_printf(c, RFB_LOGDEBUG, "Using native pixfmt"); } c->rc_sinfo.rsi_pixfmt = msg.rp_pixfmt; c->rc_crc_reset = true; return (true); } static bool rfb_recv_set_encodings(rfb_client_t *c) { rfb_cs_encodings_msg_t msg; rfb_printf(c, RFB_LOGDEBUG, "received encodings"); if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); msg.re_numencs = htons(msg.re_numencs); rfb_printf(c, RFB_LOGDEBUG, "%d values", msg.re_numencs); for (uint_t i = 0; i < msg.re_numencs; i++) { uint32_t enc; if (stream_read(c->rc_fd, &enc, sizeof (enc)) != sizeof (enc)) return (false); enc = htonl(enc); switch (enc) { case RFBP_ENCODING_RAW: rfb_printf(c, RFB_LOGDEBUG, "client supports raw encoding"); c->rc_encodings |= RFB_ENCODING_RAW; break; case RFBP_ENCODING_ZLIB: rfb_printf(c, RFB_LOGDEBUG, "client supports zlib encoding"); if (!(c->rc_encodings & RFB_ENCODING_ZLIB)) { if (deflateInit(&c->rc_zstream, Z_BEST_SPEED) != Z_OK) { return (false); } c->rc_encodings |= RFB_ENCODING_ZLIB; } break; case RFBP_ENCODING_RESIZE: rfb_printf(c, RFB_LOGDEBUG, "client supports resize"); c->rc_encodings |= RFB_ENCODING_RESIZE; break; case RFBP_ENCODING_EXT_KEVENT: rfb_printf(c, RFB_LOGDEBUG, "client supports ext key event"); c->rc_encodings |= RFB_ENCODING_EXT_KEVENT; break; case RFBP_ENCODING_DESKTOP_NAME: rfb_printf(c, RFB_LOGDEBUG, "client supports desktop name"); c->rc_encodings |= RFB_ENCODING_DESKTOP_NAME; break; default: rfb_printf(c, RFB_LOGDEBUG, "client supports encoding %d", (int32_t)enc); } } return (true); } static bool rfb_recv_update(rfb_client_t *c) { rfb_cs_update_msg_t msg; if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); if (!c->rc_keyevent_sent && (c->rc_encodings & RFB_ENCODING_EXT_KEVENT)) { /* * Take this opportunity to tell the client that we * accept QEMU Extended Key Event Pseudo-encoding. */ c->rc_keyevent_sent = true; rfb_send_extended_keyevent_update_msg(c); } c->rc_pending = true; if (msg.rum_incremental == 0) { rfb_printf(c, RFB_LOGDEBUG, "client requested full screen update"); c->rc_send_fullscreen = true; } return (true); } static bool rfb_recv_key_event(rfb_client_t *c) { rfb_cs_key_event_msg_t msg; if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); msg.rke_sym = htonl(msg.rke_sym); rfb_printf(c, RFB_LOGDEBUG, "received key %s %x", msg.rke_down == 0 ? "up" : "down", msg.rke_sym); console_key_event(msg.rke_down, msg.rke_sym, htonl(0)); c->rc_input_detected = true; return (true); } static bool rfb_recv_pointer_event(rfb_client_t *c) { rfb_cs_pointer_event_msg_t msg; if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); msg.rpe_x = htons(msg.rpe_x); msg.rpe_y = htons(msg.rpe_y); if (rfb_debug > 1) { rfb_printf(c, RFB_LOGDEBUG, "received pointer event @ %dx%d", msg.rpe_x, msg.rpe_y); } console_ptr_event(msg.rpe_button, msg.rpe_x, msg.rpe_y); c->rc_input_detected = true; return (true); } static bool rfb_recv_cut_text(rfb_client_t *c) { rfb_cs_cut_text_msg_t msg; unsigned char buf[32]; rfb_printf(c, RFB_LOGDEBUG, "received cut text event"); if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); msg.rct_length = htonl(msg.rct_length); rfb_printf(c, RFB_LOGDEBUG, "%u bytes in buffer", msg.rct_length); /* Consume the buffer */ while (msg.rct_length > 0) { ssize_t l; l = stream_read(c->rc_fd, buf, MIN(sizeof (buf), msg.rct_length)); if (l <= 0) return (false); msg.rct_length -= l; } return (true); } static bool rfb_recv_qemu(rfb_client_t *c) { rfb_cs_qemu_msg_t msg; rfb_printf(c, RFB_LOGDEBUG, "received QEMU event"); if (stream_read(c->rc_fd, &msg, sizeof (msg)) != sizeof (msg)) return (false); switch (msg.rq_subtype) { case RFBP_CS_QEMU_KEVENT: { rfb_cs_qemu_extended_key_msg_t keymsg; if (stream_read(c->rc_fd, &keymsg, sizeof (keymsg)) != sizeof (keymsg)) { return (false); } keymsg.rqek_sym = htonl(keymsg.rqek_sym); keymsg.rqek_code = htonl(keymsg.rqek_code); rfb_printf(c, RFB_LOGDEBUG, "QEMU key %s %x / %x", keymsg.rqek_down == 0 ? "up" : "down", keymsg.rqek_sym, keymsg.rqek_code); console_key_event((int)keymsg.rqek_down, keymsg.rqek_sym, keymsg.rqek_code); c->rc_input_detected = true; break; } default: rfb_printf(c, RFB_LOGWARN, "Unknown QEMU event subtype: %d\n", msg.rq_subtype); return (false); } return (true); } static bool rfb_send_update_header(rfb_client_t *c, int numrects) { rfb_server_update_msg_t msg; msg.rss_type = RFBP_SC_UPDATE; msg.rss_pad = 0; msg.rss_numrects = htons(numrects); return (stream_write(c->rc_fd, &msg, sizeof (msg)) == sizeof (msg)); } static void rfb_send_resize_update_msg(rfb_client_t *c) { rfb_rect_hdr_t rect; rfb_printf(c, RFB_LOGDEBUG, "sending screen resize %dx%d", c->rc_width, c->rc_height); (void) rfb_send_update_header(c, 1); rect.rr_x = htons(0); rect.rr_y = htons(0); rect.rr_width = htons(c->rc_width); rect.rr_height = htons(c->rc_height); rect.rr_encoding = htonl(RFBP_ENCODING_RESIZE); (void) stream_write(c->rc_fd, &rect, sizeof (rect)); } static void rfb_send_extended_keyevent_update_msg(rfb_client_t *c) { rfb_rect_hdr_t rect; rfb_printf(c, RFB_LOGDEBUG, "sending extended keyevent update message"); (void) rfb_send_update_header(c, 1); rect.rr_x = htons(0); rect.rr_y = htons(0); rect.rr_width = htons(c->rc_width); rect.rr_height = htons(c->rc_height); rect.rr_encoding = htonl(RFBP_ENCODING_EXT_KEVENT); (void) stream_write(c->rc_fd, &rect, sizeof (rect)); } static void translate_pixels(rfb_client_t *c, struct bhyvegc_image *gci, int x1, int y1, int x2, int y2) { rfb_pixfmt_t *px = &c->rc_sinfo.rsi_pixfmt; rfb_pixfmt_t *spx = &c->rc_s->rs_pixfmt; int w, h; w = gci->width; h = gci->height; VERIFY3S(gci->width, ==, c->rc_gci.width); VERIFY3S(gci->height, ==, c->rc_gci.height); for (uint_t y = y1; y < h && y < y2; y++) { for (uint_t x = x1; x < w && x < x2; x++) { uint32_t p; p = gci->data[y * w + x]; c->rc_gci.data[y * w + x] = 0xff000000 | ((p >> spx->rp_r_shift) & 0xff) << px->rp_r_shift | ((p >> spx->rp_g_shift) & 0xff) << px->rp_g_shift | ((p >> spx->rp_b_shift) & 0xff) << px->rp_b_shift; } } } static bool rfb_send_rect(rfb_client_t *c, struct bhyvegc_image *gci, int x, int y, int w, int h) { rfb_rect_hdr_t rect; unsigned long zlen; ssize_t nwrite, total; int err; uint32_t *p; uint8_t *zbufp; if (rfb_debug > 1) { rfb_printf(c, RFB_LOGDEBUG, "send rect %dx%d %dx%d", x, y, w, h); } /* Rectangle header. */ rect.rr_x = htons(x); rect.rr_y = htons(y); rect.rr_width = htons(w); rect.rr_height = htons(h); uint32_t *data = gci->data; if (c->rc_custom_pixfmt) { translate_pixels(c, gci, x, y, x + w, y + h); data = c->rc_gci.data; } h = y + h; w *= sizeof (uint32_t); if (c->rc_encodings & RFB_ENCODING_ZLIB) { zbufp = c->rc_zbuf; c->rc_zstream.total_in = 0; c->rc_zstream.total_out = 0; for (p = &data[y * gci->width + x]; y < h; y++) { c->rc_zstream.next_in = (Bytef *)p; c->rc_zstream.avail_in = w; c->rc_zstream.next_out = (Bytef *)zbufp; c->rc_zstream.avail_out = RFB_ZLIB_BUFSZ + 16 - c->rc_zstream.total_out; c->rc_zstream.data_type = Z_BINARY; /* Compress with zlib. */ err = deflate(&c->rc_zstream, Z_SYNC_FLUSH); if (err != Z_OK) { rfb_printf(c, RFB_LOGWARN, "zlib[rect] deflate err: %d", err); goto doraw; } zbufp = c->rc_zbuf + c->rc_zstream.total_out; p += gci->width; } rect.rr_encoding = htonl(RFBP_ENCODING_ZLIB); nwrite = stream_write(c->rc_fd, &rect, sizeof (rect)); if (nwrite <= 0) return (false); zlen = htonl(c->rc_zstream.total_out); nwrite = stream_write(c->rc_fd, &zlen, sizeof (uint32_t)); if (nwrite <= 0) return (false); return (stream_write(c->rc_fd, c->rc_zbuf, c->rc_zstream.total_out) == c->rc_zstream.total_out); } doraw: total = 0; zbufp = c->rc_zbuf; for (p = &data[y * gci->width + x]; y < h; y++) { memcpy(zbufp, p, w); zbufp += w; total += w; p += gci->width; } rect.rr_encoding = htonl(RFBP_ENCODING_RAW); nwrite = stream_write(c->rc_fd, &rect, sizeof (rect)); if (nwrite <= 0) return (false); return (stream_write(c->rc_fd, c->rc_zbuf, total) == total); } static bool rfb_send_all(rfb_client_t *c, struct bhyvegc_image *gci) { rfb_rect_hdr_t rect; ssize_t nwrite; unsigned long zlen; int err; rfb_printf(c, RFB_LOGDEBUG, "send entire screen"); /* Just the one (big) rect. */ if (!rfb_send_update_header(c, 1)) return (false); rect.rr_x = 0; rect.rr_y = 0; rect.rr_width = htons(gci->width); rect.rr_height = htons(gci->height); uint32_t *data = gci->data; if (c->rc_custom_pixfmt) { translate_pixels(c, gci, 0, 0, gci->width, gci->height); data = c->rc_gci.data; } if (c->rc_encodings & RFB_ENCODING_ZLIB) { c->rc_zstream.next_in = (Bytef *)data; c->rc_zstream.avail_in = gci->width * gci->height * sizeof (uint32_t); c->rc_zstream.next_out = (Bytef *)c->rc_zbuf; c->rc_zstream.avail_out = RFB_ZLIB_BUFSZ + 16; c->rc_zstream.data_type = Z_BINARY; c->rc_zstream.total_in = 0; c->rc_zstream.total_out = 0; /* Compress with zlib. */ err = deflate(&c->rc_zstream, Z_SYNC_FLUSH); if (err != Z_OK) { rfb_printf(c, RFB_LOGWARN, "zlib deflate err: %d", err); goto doraw; } rect.rr_encoding = htonl(RFBP_ENCODING_ZLIB); nwrite = stream_write(c->rc_fd, &rect, sizeof (rect)); if (nwrite <= 0) return (false); zlen = htonl(c->rc_zstream.total_out); nwrite = stream_write(c->rc_fd, &zlen, sizeof (uint32_t)); if (nwrite <= 0) return (false); return (stream_write(c->rc_fd, c->rc_zbuf, c->rc_zstream.total_out) == c->rc_zstream.total_out); } doraw: rect.rr_encoding = htonl(RFBP_ENCODING_RAW); nwrite = stream_write(c->rc_fd, &rect, sizeof (rect)); if (nwrite <= 0) return (false); nwrite = gci->width * gci->height * sizeof (uint32_t); return (stream_write(c->rc_fd, data, nwrite) == nwrite); } static bool rfb_send_screen(rfb_client_t *c) { struct bhyvegc_image *gci; bool retval = true; bool sendall = false; int xcells, ycells; int rem_x, rem_y; uint32_t *p, *ncrc, *ocrc; uint_t changes, perc, x, y; /* Updates require a preceding client update request. */ if (atomic_exchange(&c->rc_pending, false) == false) return (true); console_refresh(); gci = console_get_image(); /* * It's helpful if the image size or data address does not change * underneath us. */ pthread_mutex_lock(&gci->mtx); /* Check for screen resolution changes. */ if (c->rc_width != gci->width || c->rc_height != gci->height) { c->rc_width = gci->width; c->rc_height = gci->height; c->rc_crc_reset = true; c->rc_send_fullscreen = true; /* If the client supports it, send a resize event. */ if (c->rc_encodings & RFB_ENCODING_RESIZE) { rfb_send_resize_update_msg(c); /* * A resize message counts as an update in response to * the client's preceding request so rc->pending does * not need to be reset here. */ goto done; } } /* Clear old CRC values. */ if (atomic_exchange(&c->rc_crc_reset, false)) memset(c->rc_crc, '\0', c->rc_cells * sizeof (uint32_t)); if (c->rc_custom_pixfmt && (c->rc_gci.data == NULL || c->rc_gci.width != c->rc_width || c->rc_gci.height != c->rc_height)) { c->rc_gci.data = reallocarray(c->rc_gci.data, c->rc_width * c->rc_height, sizeof (uint32_t)); if (c->rc_gci.data == NULL) { retval = false; goto done; } c->rc_gci.width = c->rc_width; c->rc_gci.height = c->rc_height; } else if (!c->rc_custom_pixfmt && c->rc_gci.data != NULL) { free(c->rc_gci.data); c->rc_gci.data = NULL; } sendall = atomic_exchange(&c->rc_send_fullscreen, false); /* * Calculate a checksum for each 32x32 cell. Send all that have * changed since the last scan. */ xcells = howmany(gci->width, RFB_PIX_PER_CELL); ycells = howmany(gci->height, RFB_PIX_PER_CELL); rem_x = gci->width & RFB_PIXCELL_MASK; rem_y = gci->height & RFB_PIXCELL_MASK; if (rem_y == 0) rem_y = RFB_PIX_PER_CELL; p = gci->data; ncrc = c->rc_crc_tmp - xcells; ocrc = c->rc_crc - xcells; changes = 0; memset(c->rc_crc_tmp, '\0', sizeof (uint32_t) * xcells * ycells); for (y = 0; y < gci->height; y++) { if ((y & RFB_PIXCELL_MASK) == 0) { ncrc += xcells; ocrc += xcells; } for (x = 0; x < xcells; x++) { uint_t cellwidth; if (x == xcells - 1 && rem_x > 0) cellwidth = rem_x; else cellwidth = RFB_PIX_PER_CELL; if (rfb_sse42) { ncrc[x] = fast_crc32(p, cellwidth * sizeof (uint32_t), ncrc[x]); } else { ncrc[x] = (uint32_t)crc32(ncrc[x], (Bytef *)p, cellwidth * sizeof (uint32_t)); } p += cellwidth; /* check for crc delta if last row in cell. */ if ((y & RFB_PIXCELL_MASK) == RFB_PIXCELL_MASK || y == gci->height - 1) { if (ocrc[x] != ncrc[x]) { ocrc[x] = ncrc[x]; ncrc[x] = 1; changes++; } else { ncrc[x] = 0; } } } } perc = (changes * 100) / (xcells * ycells); if (rfb_debug > 1 && changes > 0) { rfb_printf(c, RFB_LOGDEBUG, "scanned and found %u changed cell(s) - %u%%", changes, perc); } /* * If there are no changes, don't send an update. Restore the pending * flag since we still owe the client an update. */ if (!sendall && !changes) { c->rc_pending = true; goto done; } /* If there are a lot of changes, send the whole screen. */ if (perc >= RFB_SENDALL_THRESH) sendall = true; if (sendall) { retval = rfb_send_all(c, gci); goto done; } if (!rfb_send_update_header(c, changes)) { retval = false; goto done; } /* Send the changed cells as separate rects. */ ncrc = c->rc_crc_tmp; for (y = 0; y < gci->height; y += RFB_PIX_PER_CELL) { /* Previous cell's row. */ int celly = (y >> RFB_PIXCELL_SHIFT); /* Delta check crc to previous set. */ for (x = 0; x < xcells; x++) { uint_t cellwidth; if (*ncrc++ == 0) continue; if (x == xcells - 1 && rem_x > 0) cellwidth = rem_x; else cellwidth = RFB_PIX_PER_CELL; if (!rfb_send_rect(c, gci, x * RFB_PIX_PER_CELL, celly * RFB_PIX_PER_CELL, cellwidth, y + RFB_PIX_PER_CELL >= gci->height ? rem_y : RFB_PIX_PER_CELL)) { retval = false; goto done; } } } done: pthread_mutex_unlock(&gci->mtx); return (retval); } static void * rfb_client_rx_thread(void *arg) { rfb_client_t *c = arg; unsigned char cmd; bool ret = true; while (ret && !c->rc_closing && (read(c->rc_fd, &cmd, 1) == 1)) { switch (cmd) { case RFBP_CS_SET_PIXEL_FORMAT: ret = rfb_recv_set_pixel_format(c); break; case RFBP_CS_SET_ENCODINGS: ret = rfb_recv_set_encodings(c); break; case RFBP_CS_UPDATE_REQUEST: ret = rfb_recv_update(c); break; case RFBP_CS_KEY_EVENT: ret = rfb_recv_key_event(c); break; case RFBP_CS_POINTER_EVENT: ret = rfb_recv_pointer_event(c); break; case RFBP_CS_CUT_TEXT: ret = rfb_recv_cut_text(c); break; case RFBP_CS_QEMU: ret = rfb_recv_qemu(c); break; default: rfb_printf(c, RFB_LOGWARN, "unknown cs code %d", cmd & 0xff); ret = false; } } rfb_printf(c, RFB_LOGDEBUG, "client rx thread exiting"); c->rc_closing = true; return (NULL); } static void * rfb_client_tx_thread(void *arg) { rfb_client_t *c = arg; rfb_server_t *s = c->rc_s; char tname[MAXCOMLEN + 1]; uint_t counter = 0; hrtime_t tprev; void *status; int err; (void) snprintf(tname, sizeof (tname), "rfb%u tx", c->rc_instance); (void) pthread_set_name_np(c->rc_tx_tid, tname); c->rc_sinfo.rsi_pixfmt = c->rc_s->rs_pixfmt; c->rc_encodings = RFB_ENCODING_RAW; if (!rfb_handshake(c)) { rfb_printf(c, RFB_LOGWARN, "handshake failure"); goto out; } c->rc_cells = howmany(RFB_MAX_WIDTH * RFB_MAX_HEIGHT, RFB_PIX_PER_CELL); if ((c->rc_crc = calloc(c->rc_cells, sizeof (uint32_t))) == NULL || (c->rc_crc_tmp = calloc(c->rc_cells, sizeof (uint32_t))) == NULL) { perror("calloc crc"); goto out; } err = pthread_create(&c->rc_rx_tid, NULL, rfb_client_rx_thread, c); if (err != 0) { perror("pthread_create client rx thread"); goto out; } (void) snprintf(tname, sizeof (tname), "rfb%u rx", c->rc_instance); (void) pthread_set_name_np(c->rc_rx_tid, tname); tprev = gethrtime(); while (!c->rc_closing) { struct timeval tv; hrtime_t tnow; int64_t tdiff; fd_set rfds; int err; FD_ZERO(&rfds); FD_SET(c->rc_fd, &rfds); tv.tv_sec = 0; tv.tv_usec = RFB_SEL_DELAY_US; err = select(c->rc_fd + 1, &rfds, NULL, NULL, &tv); if (err < 0) break; /* Determine if its time to push the screen; ~24hz. */ tnow = gethrtime(); tdiff = NSEC2USEC(tnow - tprev); if (tdiff >= RFB_SCREEN_POLL_DELAY) { bool input; tprev = tnow; input = atomic_exchange(&c->rc_input_detected, false); /* * Refresh the screen on every second trip through the * loop, or if keyboard/mouse input has been detected. */ if ((++counter & 1) != 0 || input) { if (!rfb_send_screen(c)) break; } } else { (void) usleep(RFB_SCREEN_POLL_DELAY - tdiff); } } out: rfb_printf(c, RFB_LOGWARN, "disconnected"); (void) pthread_join(c->rc_rx_tid, &status); pthread_mutex_lock(&s->rs_clientlock); s->rs_clientcount--; list_remove(&s->rs_clients, c); if (s->rs_exclusive && s->rs_clientcount == 0) s->rs_exclusive = false; id_free(rfb_idspace, c->rc_instance); pthread_mutex_unlock(&s->rs_clientlock); rfb_free_client(c); return (NULL); } static void rfb_accept(int sfd, enum ev_type event, void *arg) { rfb_server_t *s = arg; rfb_client_t *c = NULL; struct sockaddr_storage cliaddr; socklen_t len; char host[NI_MAXHOST], port[NI_MAXSERV]; int cfd, err; uint_t cc; rfb_printf(c, RFB_LOGDEBUG, "incoming connection"); len = sizeof (cliaddr); cfd = accept(sfd, (struct sockaddr *)&cliaddr, &len); if (cfd == -1) { perror("client accept"); return; } *host = *port = '\0'; if (cliaddr.ss_family == AF_UNIX) { rfb_printf(NULL, RFB_LOGDEBUG, "connection on UNIX socket"); (void) strlcpy(host, "", sizeof (host)); } else { err = getnameinfo((struct sockaddr *)&cliaddr, len, host, sizeof (host), port, sizeof (port), NI_NUMERICHOST | NI_NUMERICSERV); if (err != 0) { rfb_printf(NULL, RFB_LOGERR, "getnameinfo: %s", gai_strerror(err)); *host = *port = '\0'; } else { rfb_printf(NULL, RFB_LOGDEBUG, "connection from %s:%s", host, port); } } pthread_mutex_lock(&s->rs_clientlock); cc = s->rs_clientcount; pthread_mutex_unlock(&s->rs_clientlock); if (cc >= RFB_MAX_CLIENTS) { rfb_printf(NULL, RFB_LOGERR, "too many clients, closing connection."); goto fail; } if ((c = calloc(1, sizeof (rfb_client_t))) == NULL) { perror("calloc client"); goto fail; } c->rc_fd = cfd; c->rc_s = s; c->rc_zbuf = malloc(RFB_ZLIB_BUFSZ + 16); if (c->rc_zbuf == NULL) goto fail; pthread_mutex_lock(&s->rs_clientlock); err = pthread_create(&c->rc_tx_tid, NULL, rfb_client_tx_thread, c); if (err != 0) { perror("pthread_create client tx thread"); pthread_mutex_unlock(&s->rs_clientlock); goto fail; } s->rs_clientcount++; list_insert_tail(&s->rs_clients, c); c->rc_instance = id_allocff(rfb_idspace); pthread_mutex_unlock(&s->rs_clientlock); (void) pthread_detach(c->rc_tx_tid); rfb_printf(c, RFB_LOGWARN, "connection from %s", host); return; fail: (void) close(cfd); free(c); } int rfb_init(char *hostname, int port, int wait, const char *password, const char *name) { rfb_server_t *s; #ifndef WITHOUT_CAPSICUM cap_rights_t rights; #endif (void) pthread_once(&rfb_once, rfb_init_once); if (rfb_idspace == NULL) { rfb_printf(NULL, RFB_LOGERR, "rfb_idspace could not be allocated"); return (-1); } if ((s = calloc(1, sizeof (rfb_server_t))) == NULL) { perror("calloc"); return (-1); } s->rs_fd = -1; s->rs_name = name; if (password != NULL && strlen(password) > 0) s->rs_password = password; if (pthread_mutex_init(&s->rs_clientlock, NULL) != 0) { perror("pthread_mutex_init"); free(s); return (-1); } list_create(&s->rs_clients, sizeof (rfb_client_t), offsetof(rfb_client_t, rc_node)); /* Server pixel format. */ s->rs_pixfmt.rp_bpp = RFB_PIX_BPP; s->rs_pixfmt.rp_depth = RFB_PIX_DEPTH; s->rs_pixfmt.rp_bigendian = 0; s->rs_pixfmt.rp_truecolour = 1; s->rs_pixfmt.rp_r_max = htons(RFB_PIX_RMAX); s->rs_pixfmt.rp_g_max = htons(RFB_PIX_GMAX); s->rs_pixfmt.rp_b_max = htons(RFB_PIX_BMAX); s->rs_pixfmt.rp_r_shift = RFB_PIX_RSHIFT; s->rs_pixfmt.rp_g_shift = RFB_PIX_GSHIFT; s->rs_pixfmt.rp_b_shift = RFB_PIX_BSHIFT; /* UNIX socket. */ if (port == -1 && hostname != NULL && *hostname == '/') { struct sockaddr_un sock; s->rs_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (s->rs_fd < 0) { perror("socket"); goto fail; } sock.sun_family = AF_UNIX; if (strlcpy(sock.sun_path, hostname, sizeof (sock.sun_path)) >= sizeof (sock.sun_path)) { rfb_printf(NULL, RFB_LOGERR, "socket path '%s' too long\n", hostname); goto fail; } (void) unlink(hostname); if (bind(s->rs_fd, (struct sockaddr *)&sock, sizeof (sock)) < 0) { perror("bind"); goto fail; } } else { struct addrinfo hints, *ai = NULL; char servname[6]; int e; (void) snprintf(servname, sizeof (servname), "%d", port ? port : RFB_DEFAULT_PORT); if (hostname == NULL || strlen(hostname) == 0) { #if defined(INET) hostname = "127.0.0.1"; #elif defined(INET6) hostname = "[::1]"; #endif } memset(&hints, '\0', sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE; if ((e = getaddrinfo(hostname, servname, &hints, &ai)) != 0) { rfb_printf(NULL, RFB_LOGERR, "getaddrinfo: %s", gai_strerror(e)); goto fail; } s->rs_fd = socket(ai->ai_family, ai->ai_socktype, 0); if (s->rs_fd < 0) { perror("socket"); freeaddrinfo(ai); goto fail; } e = 1; (void) setsockopt(s->rs_fd, SOL_SOCKET, SO_REUSEADDR, &e, sizeof (e)); if (bind(s->rs_fd, ai->ai_addr, ai->ai_addrlen) < 0) { perror("bind"); freeaddrinfo(ai); goto fail; } freeaddrinfo(ai); } if (listen(s->rs_fd, 5) < 0) { perror("listen"); goto fail; } #ifndef WITHOUT_CAPSICUM cap_rights_init(&rights, CAP_ACCEPT, CAP_EVENT, CAP_READ, CAP_WRITE); if (caph_rights_limit(s->rs_fd, &rights) == -1) errx(EX_OSERR, "Unable to apply rights for sandbox"); #endif s->rs_connevent = mevent_add(s->rs_fd, EVF_READ, rfb_accept, s); if (s->rs_connevent == NULL) { rfb_printf(NULL, RFB_LOGERR, "Failed to set up rfb connection mevent"); goto fail; } list_insert_tail(&rfb_list, s); /* * Wait for first connection. Since the mevent thread is * not yet running, we can't rely on normal incoming connection * handling. */ if (wait != 0) { fd_set rfds; int e; rfb_printf(NULL, RFB_LOGWARN, "holding boot until first client connection"); for (;;) { FD_ZERO(&rfds); FD_SET(s->rs_fd, &rfds); e = select(s->rs_fd + 1, &rfds, NULL, NULL, NULL); if (e < 0 && errno == EINTR) continue; if (e < 0 || FD_ISSET(s->rs_fd, &rfds)) break; } rfb_printf(NULL, RFB_LOGWARN, "continuing boot"); } return (0); fail: if (s->rs_fd != -1) VERIFY3S(close(s->rs_fd), ==, 0); (void) pthread_mutex_destroy(&s->rs_clientlock); list_destroy(&s->rs_clients); free(s); return (-1); }