/** * xrdp: A Remote Desktop Protocol server. * * Copyright (C) Jay Sorg 2004-2015 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * libvnc * * The message definitions used in this source file can be found mostly * in RFC6143 - "The Remote Framebuffer Protocol". * * The ExtendedDesktopSize encoding is reserved in RFC6143, but not * documented there. It is documented by the RFB protocol community * wiki currently held at https://github.com/rfbproto/rfbroto. This is * referred to below as the "RFB community wiki" */ #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include "vnc.h" #include "vnc_clip.h" #include "rfb.h" #include "log.h" #include "timers.h" #include "trans.h" #include "ssl_calls.h" #include "string_calls.h" #include "xrdp_client_info.h" /* elements in above list */ #define EDS_STATUS_MSG_COUNT \ (sizeof(eds_status_msg) / sizeof(eds_status_msg[0])) /* Used by enabled_encodings_mask */ enum { MSK_EXTENDED_DESKTOP_SIZE = (1 << 0) }; enum { /** Time to wait for a forwarded resize to complete */ FORWARDED_RESIZE_TIMEOUT = 1500 /* milli-seconds */ }; /******************************************************************************/ int lib_send_copy(struct vnc *v, struct stream *s) { return trans_write_copy_s(v->trans, s); } /******************************************************************************/ /* taken from vncauth.c */ /* performing the des3 crypt on the password so it can not be seen on the wire 'bytes' in, contains 16 bytes server random out, random and 'passwd' conbined */ static void rfbEncryptBytes(char *bytes, const char *passwd) { char key[24]; void *des; int len; /* key is simply password padded with nulls */ g_memset(key, 0, sizeof(key)); len = MIN(g_strlen(passwd), 8); g_mirror_memcpy(key, passwd, len); des = ssl_des3_encrypt_info_create(key, 0); ssl_des3_encrypt(des, 8, bytes, bytes); ssl_des3_info_delete(des); des = ssl_des3_encrypt_info_create(key, 0); ssl_des3_encrypt(des, 8, bytes + 8, bytes + 8); ssl_des3_info_delete(des); } /******************************************************************************/ /* sha1 hash 'passwd', create a string from the hash and call rfbEncryptBytes */ static void rfbHashEncryptBytes(char *bytes, const char *passwd) { char passwd_hash[20]; char passwd_hash_text[40]; void *sha1; int passwd_bytes; /* create password hash from password */ passwd_bytes = g_strlen(passwd); sha1 = ssl_sha1_info_create(); ssl_sha1_clear(sha1); ssl_sha1_transform(sha1, "xrdp_vnc", 8); ssl_sha1_transform(sha1, passwd, passwd_bytes); ssl_sha1_transform(sha1, passwd, passwd_bytes); ssl_sha1_complete(sha1, passwd_hash); ssl_sha1_info_delete(sha1); g_snprintf(passwd_hash_text, 39, "%2.2x%2.2x%2.2x%2.2x", (tui8)passwd_hash[0], (tui8)passwd_hash[1], (tui8)passwd_hash[2], (tui8)passwd_hash[3]); passwd_hash_text[39] = 0; rfbEncryptBytes(bytes, passwd_hash_text); } /**************************************************************************//** * Logs a debug message containing a screen layout * * @param lvl Level to log at * @param source Where the layout came from * @param layout Layout to log */ static void log_screen_layout(const enum logLevels lvl, const char *source, const struct vnc_screen_layout *layout) { unsigned int i; char text[256]; size_t pos; int res; pos = 0; res = g_snprintf(text, sizeof(text) - pos, "Layout from %s (geom=%dx%d #screens=%u) :", source, layout->total_width, layout->total_height, layout->count); i = 0; while (res > 0 && (size_t)res < sizeof(text) - pos && i < layout->count) { pos += res; res = g_snprintf(&text[pos], sizeof(text) - pos, " %d:(%dx%d+%d+%d)", layout->s[i].id, layout->s[i].width, layout->s[i].height, layout->s[i].x, layout->s[i].y); ++i; } LOG(lvl, "%s", text); } /**************************************************************************//** * Compares two vnc_screen structures * * @param a First structure * @param b Second structure * * @return Suitable for sorting structures on (x, y, width, height) */ static int cmp_vnc_screen(const struct vnc_screen *a, const struct vnc_screen *b) { int result = 0; if (a->x != b->x) { result = a->x - b->x; } else if (a->y != b->y) { result = a->y - b->y; } else if (a->width != b->width) { result = a->width - b->width; } else if (a->height != b->height) { result = a->height - b->height; } return result; } /**************************************************************************//** * Compares two vnc_screen_layout structures for equality * @param a First layout * @param b First layout * @return != 0 if structures are equal */ static int vnc_screen_layouts_equal(const struct vnc_screen_layout *a, const struct vnc_screen_layout *b) { unsigned int i; int result = (a->total_width == b->total_width && a->total_height == b->total_height && a->count == b->count); if (result) { for (i = 0 ; result && i < a->count ; ++i) { result = (cmp_vnc_screen(&a->s[i], &b->s[i]) == 0); } } return result; } /**************************************************************************//** * Reads an extended desktop size rectangle from the VNC server * * @param v VNC object * @param [out] layout Desired layout for server * @return != 0 for error * * @pre The next octet read from v->trans is the number of screens * * @post Returned structure is in increasing ID order * @post layout->total_width is untouched * @post layout->total_height is untouched */ static int read_extended_desktop_size_rect(struct vnc *v, struct vnc_screen_layout *layout) { struct stream *s; int error; unsigned int count; layout->count = 0; make_stream(s); init_stream(s, 8192); /* Read in the current screen config */ error = trans_force_read_s(v->trans, s, 4); if (error == 0) { /* Get the number of screens */ in_uint8(s, count); if (count <= 0 || count > CLIENT_MONITOR_DATA_MAXIMUM_MONITORS) { LOG(LOG_LEVEL_ERROR, "Bad monitor count %d in ExtendedDesktopSize rectangle", count); error = 1; } else { in_uint8s(s, 3); error = trans_force_read_s(v->trans, s, 16 * count); if (error == 0) { unsigned int i; for (i = 0 ; i < count ; ++i) { in_uint32_be(s, layout->s[i].id); in_uint16_be(s, layout->s[i].x); in_uint16_be(s, layout->s[i].y); in_uint16_be(s, layout->s[i].width); in_uint16_be(s, layout->s[i].height); in_uint32_be(s, layout->s[i].flags); } /* sort monitors in increasing (x,y) order */ qsort(layout->s, count, sizeof(layout->s[0]), (int (*)(const void *, const void *))cmp_vnc_screen); layout->count = count; } } } free_stream(s); return error; } /**************************************************************************//** * Sends a SetDesktopSize message * * @param v VNC object * @param layout Desired layout for server * @return != 0 for error * * The SetDesktopSize message is documented in the RFB community wiki * "SetDesktopSize" section. */ static int send_set_desktop_size(struct vnc *v, const struct vnc_screen_layout *layout) { unsigned int i; struct stream *s; int error; make_stream(s); init_stream(s, 8192); out_uint8(s, 251); out_uint8(s, 0); out_uint16_be(s, layout->total_width); out_uint16_be(s, layout->total_height); out_uint8(s, layout->count); out_uint8(s, 0); for (i = 0 ; i < layout->count ; ++i) { out_uint32_be(s, layout->s[i].id); out_uint16_be(s, layout->s[i].x); out_uint16_be(s, layout->s[i].y); out_uint16_be(s, layout->s[i].width); out_uint16_be(s, layout->s[i].height); out_uint32_be(s, layout->s[i].flags); } s_mark_end(s); LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: Sending SetDesktopSize %dx%d", layout->total_width, layout->total_height); error = lib_send_copy(v, s); free_stream(s); return error; } /**************************************************************************//** * Initialises a vnc_screen_layout as a single screen * @param[in] width Screen Width * @param[in] height Screen Height * @param[out] layout Layout to initialise */ static void init_single_screen_layout(int width, int height, struct vnc_screen_layout *layout) { layout->total_width = width; layout->total_height = height; layout->count = 1; layout->s[0].id = 0; layout->s[0].x = 0; layout->s[0].y = 0; layout->s[0].width = width; layout->s[0].height = height; layout->s[0].flags = 0; } /**************************************************************************//** * Resize the client to match the server_layout * * @param v VNC object * @param update_in_progress True if there's a painter update in progress * @return != 0 for error * * The new client layout is recorded in v->client_layout. */ static int resize_client_to_server(struct vnc *v, int update_in_progress) { int error = 0; unsigned int i; const struct vnc_screen_layout *sl = &v->server_layout; struct monitor_info client_mons[CLIENT_MONITOR_DATA_MAXIMUM_MONITORS] = {0}; if (sl->count <= 0 || sl->count > CLIENT_MONITOR_DATA_MAXIMUM_MONITORS) { LOG(LOG_LEVEL_ERROR, "%s: Programming error. Bad monitors %d", __func__, sl->count); return 1; } // Convert the server monitors into client monitors for (i = 0; i < sl->count; ++i) { client_mons[i].left = sl->s[i].x; client_mons[i].top = sl->s[i].y; client_mons[i].right = sl->s[i].x + sl->s[i].width - 1; client_mons[i].bottom = sl->s[i].y + sl->s[i].height - 1; } if (update_in_progress && v->server_end_update(v) != 0) { error = 1; } else { error = v->client_monitor_resize(v, sl->total_width, sl->total_height, sl->count, client_mons); if (error == 0) { v->client_layout = *sl; } if (update_in_progress && v->server_begin_update(v) != 0) { error = 1; } } return error; } /**************************************************************************//** * Resize the server to the client layout * * @param v VNC object * @return != 0 for error * * The new client layout is recorded in v->client_layout. */ static int resize_server_to_client_layout(struct vnc *v) { int error = 0; /* Before checking the 'resize_supported' flag, see if this * is a null operation. We can get here if the server doesn't * support resize, and we've queued a request to resize the client * to the server size */ if (vnc_screen_layouts_equal(&v->server_layout, &v->client_layout)) { LOG(LOG_LEVEL_DEBUG, "Server layout is the same " "as the client layout"); v->resize_status = VRS_DONE; } else if (v->resize_supported != VRSS_SUPPORTED) { LOG(LOG_LEVEL_ERROR, "%s: Asked to resize server, but not possible", __func__); error = 1; } else { /* * If we've only got one screen, and the other side has * only got one screen, we will preserve their screen ID * and any flags. This may prevent us sending an unwanted * SetDesktopSize message if the screen dimensions are * a match. We can't do this with more than one screen, * as we have no way to map different IDs */ if (v->server_layout.count == 1 && v->client_layout.count == 1) { LOG(LOG_LEVEL_DEBUG, "VNC " "setting screen id to %d from server", v->server_layout.s[0].id); v->client_layout.s[0].id = v->server_layout.s[0].id; v->client_layout.s[0].flags = v->server_layout.s[0].flags; } LOG(LOG_LEVEL_DEBUG, "Changing server layout"); error = send_set_desktop_size(v, &v->client_layout); v->resize_status = VRS_WAITING_FOR_RESIZE_CONFIRM; } return error; } /*****************************************************************************/ /** * Process keysym messages * @param v Module * @param keysym Keysym of keypress * @param keydown boolean - is key down? * @return != 0 for error */ static int process_keysym_msg(struct vnc *v, int keysym, int keydown) { struct stream *s = NULL; int error = 0; if (keysym > 0) { make_stream(s); /* Break key processing [MS-RDPBCGR] 2.2.8.1.1.3.1.1.1 */ if (v->ignore_next_numlock) { v->ignore_next_numlock = 0; if (keysym == XK_Num_Lock) { goto end_keysym_msg; } } if (keysym == XK_ISO_Level3_Shift) /* altgr */ { if (v->shift_state) { /* fix for mstsc sending left control down with altgr */ init_stream(s, 64); out_uint8(s, RFB_C2S_KEY_EVENT); out_uint8(s, 0); /* down flag */ out_uint8s(s, 2); out_uint32_be(s, XK_Control_L); /* left control */ s_mark_end(s); error = lib_send_copy(v, s); if (error != 0) { goto end_keysym_msg; } } } init_stream(s, 64); out_uint8(s, RFB_C2S_KEY_EVENT); out_uint8(s, keydown ? 1 : 0); out_uint8s(s, 2); out_uint32_be(s, keysym); s_mark_end(s); error = lib_send_copy(v, s); switch (keysym) { case XK_Control_L: /* left control */ v->shift_state = keydown; break; case XK_Pause: // [MS-RDPBCGR] 2.2.8.1.1.3.1.1.1 - A pause key scancode // (up or down) is always immediately followed by a // numlock key which we should ignore v->ignore_next_numlock = 1; break; default: break; } } end_keysym_msg: free_stream(s); return error; } /*****************************************************************************/ static int lib_mod_event(struct vnc *v, int msg, long param1, long param2, long param3, long param4) { struct stream *s; int error; int x; int y; int cx; int cy; int size; int total_size; int chanid; int flags; char *data; error = 0; make_stream(s); if (msg == WM_CHANNEL_DATA) { chanid = LOWORD(param1); flags = HIWORD(param1); size = (int)param2; data = (char *)param3; total_size = (int)param4; if ((size >= 0) && (size <= (32 * 1024)) && (data != 0)) { if (chanid == v->clip_chanid) { error = vnc_clip_process_channel_data(v, data, size, total_size, flags); } else { LOG(LOG_LEVEL_DEBUG, "lib_process_channel_data: unknown chanid: " "%d :(v->clip_chanid) %d", chanid, v->clip_chanid); } } else { error = 1; } } else if ((msg >= 15) && (msg <= 16)) /* key events */ { error = process_keysym_msg(v, param2, (msg == 15)); } /* mouse events * * VNC supports up to 8 mouse buttons because mouse buttons are * represented by 7 bits bitmask */ else if (msg >= WM_MOUSEMOVE && msg <= WM_BUTTON8DOWN) /* 100 to 116 */ { switch (msg) { case WM_MOUSEMOVE: break; case WM_LBUTTONUP: v->mod_mouse_state &= ~1; break; case WM_LBUTTONDOWN: v->mod_mouse_state |= 1; break; case WM_RBUTTONUP: v->mod_mouse_state &= ~4; break; case WM_RBUTTONDOWN: v->mod_mouse_state |= 4; break; case WM_BUTTON3UP: v->mod_mouse_state &= ~2; break; case WM_BUTTON3DOWN: v->mod_mouse_state |= 2; break; case WM_BUTTON4UP: v->mod_mouse_state &= ~8; break; case WM_BUTTON4DOWN: v->mod_mouse_state |= 8; break; case WM_BUTTON5UP: v->mod_mouse_state &= ~16; break; case WM_BUTTON5DOWN: v->mod_mouse_state |= 16; break; case WM_BUTTON6UP: v->mod_mouse_state &= ~32; break; case WM_BUTTON6DOWN: v->mod_mouse_state |= 32; break; case WM_BUTTON7UP: v->mod_mouse_state &= ~64; break; case WM_BUTTON7DOWN: v->mod_mouse_state |= 64; break; case WM_BUTTON8UP: v->mod_mouse_state &= ~128; break; case WM_BUTTON8DOWN: v->mod_mouse_state |= 128; break; } init_stream(s, 8192); out_uint8(s, RFB_C2S_POINTER_EVENT); out_uint8(s, v->mod_mouse_state); out_uint16_be(s, param1); out_uint16_be(s, param2); s_mark_end(s); error = lib_send_copy(v, s); } else if (msg == 200) /* invalidate */ { if (v->suppress_output == 0) { init_stream(s, 8192); out_uint8(s, RFB_C2S_FRAMEBUFFER_UPDATE_REQUEST); out_uint8(s, 0); /* incremental == 0 : Full contents */ x = (param1 >> 16) & 0xffff; out_uint16_be(s, x); y = param1 & 0xffff; out_uint16_be(s, y); cx = (param2 >> 16) & 0xffff; out_uint16_be(s, cx); cy = param2 & 0xffff; out_uint16_be(s, cy); s_mark_end(s); error = lib_send_copy(v, s); } } free_stream(s); return error; } //****************************************************************************** static int get_pixel_safe(char *data, int x, int y, int width, int height, int bpp) { int start; int shift; if (x < 0) { return 0; } if (y < 0) { return 0; } if (x >= width) { return 0; } if (y >= height) { return 0; } if (bpp == 1) { width = (width + 7) / 8; start = (y * width) + x / 8; shift = x % 8; return (data[start] & (0x80 >> shift)) != 0; } else if (bpp == 4) { width = (width + 1) / 2; start = y * width + x / 2; shift = x % 2; if (shift == 0) { return (data[start] & 0xf0) >> 4; } else { return data[start] & 0x0f; } } else if (bpp == 8) { return *(((unsigned char *)data) + (y * width + x)); } else if (bpp == 15 || bpp == 16) { return *(((unsigned short *)data) + (y * width + x)); } else if (bpp == 24 || bpp == 32) { return *(((unsigned int *)data) + (y * width + x)); } else { LOG(LOG_LEVEL_ERROR, "error in get_pixel_safe bpp %d", bpp); } return 0; } /******************************************************************************/ static void set_pixel_safe(char *data, int x, int y, int width, int height, int bpp, int pixel) { int start; int shift; if (x < 0) { return; } if (y < 0) { return; } if (x >= width) { return; } if (y >= height) { return; } if (bpp == 1) { width = (width + 7) / 8; start = (y * width) + x / 8; shift = x % 8; if (pixel & 1) { data[start] = data[start] | (0x80 >> shift); } else { data[start] = data[start] & ~(0x80 >> shift); } } else if (bpp == 15 || bpp == 16) { *(((unsigned short *)data) + (y * width + x)) = pixel; } else if (bpp == 24) { *(data + (3 * (y * width + x)) + 0) = pixel >> 0; *(data + (3 * (y * width + x)) + 1) = pixel >> 8; *(data + (3 * (y * width + x)) + 2) = pixel >> 16; } else { LOG(LOG_LEVEL_ERROR, "error in set_pixel_safe bpp %d", bpp); } } /******************************************************************************/ static int split_color(int pixel, int *r, int *g, int *b, int bpp, int *palette) { if (bpp == 8) { if (pixel >= 0 && pixel < 256 && palette != 0) { *r = (palette[pixel] >> 16) & 0xff; *g = (palette[pixel] >> 8) & 0xff; *b = (palette[pixel] >> 0) & 0xff; } } else if (bpp == 15) { *r = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); *g = ((pixel >> 2) & 0xf8) | ((pixel >> 8) & 0x7); *b = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); } else if (bpp == 16) { *r = ((pixel >> 8) & 0xf8) | ((pixel >> 13) & 0x7); *g = ((pixel >> 3) & 0xfc) | ((pixel >> 9) & 0x3); *b = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); } else if (bpp == 24 || bpp == 32) { *r = (pixel >> 16) & 0xff; *g = (pixel >> 8) & 0xff; *b = pixel & 0xff; } else { LOG(LOG_LEVEL_ERROR, "error in split_color bpp %d", bpp); } return 0; } /******************************************************************************/ static int make_color(int r, int g, int b, int bpp) { if (bpp == 24) { return (r << 16) | (g << 8) | b; } else { LOG(LOG_LEVEL_ERROR, "error in make_color bpp %d", bpp); } return 0; } /** * Converts a bits-per-pixel value to bytes-per-pixel */ static int get_bytes_per_pixel(int bpp) { int result = (bpp + 7) / 8; if (result == 3) { result = 4; } return result; } /**************************************************************************//** * Skips the specified number of bytes from the transport * * @param trans Transport to read * @param bytes Bytes to skip * @return != 0 for error */ int skip_trans_bytes(struct trans *trans, unsigned int bytes) { struct stream *s; int error = 0; make_stream(s); while (error == 0 && bytes > 0) { int chunk_size = MIN(32768, bytes); init_stream(s, chunk_size); error = trans_force_read_s(trans, s, chunk_size); bytes -= chunk_size; } free_stream(s); return error; } /**************************************************************************//** * Reads an encoding from the input stream and discards it * * @param v VNC object * @param x Encoding X value * @param y Encoding Y value * @param cx Encoding CX value * @param cy Encoding CY value * @param encoding Code for encoding * @return != 0 for error * * @pre On entry the input stream is positioned after the encoding header */ static int skip_encoding(struct vnc *v, int x, int y, int cx, int cy, encoding_type encoding) { char text[256]; int error = 0; switch (encoding) { case RFB_ENC_RAW: { int need_size = cx * cy * get_bytes_per_pixel(v->server_bpp); LOG(LOG_LEVEL_DEBUG, "Skipping RFB_ENC_RAW encoding"); error = skip_trans_bytes(v->trans, need_size); } break; case RFB_ENC_COPY_RECT: { LOG(LOG_LEVEL_DEBUG, "Skipping RFB_ENC_COPY_RECT encoding"); error = skip_trans_bytes(v->trans, 4); } break; case RFB_ENC_CURSOR: { int j = cx * cy * get_bytes_per_pixel(v->server_bpp); int k = ((cx + 7) / 8) * cy; LOG(LOG_LEVEL_DEBUG, "Skipping RFB_ENC_CURSOR encoding"); error = skip_trans_bytes(v->trans, j + k); } break; case RFB_ENC_DESKTOP_SIZE: LOG(LOG_LEVEL_DEBUG, "Skipping RFB_ENC_DESKTOP_SIZE encoding"); break; case RFB_ENC_EXTENDED_DESKTOP_SIZE: { struct vnc_screen_layout layout = {0}; LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: Skipping RFB_ENC_EXTENDED_DESKTOP_SIZE encoding " "x=%d, y=%d geom=%dx%d", x, y, cx, cy); error = read_extended_desktop_size_rect(v, &layout); } break; default: g_sprintf(text, "VNC error in skip_encoding " "encoding = %8.8x", encoding); v->server_msg(v, text, 1); } return error; } /**************************************************************************//** * Checks the size parameters from a framebuffer update are sane * @param cx Width of update * @param cy height of update * @return 0 if the proposed sizes could result in overflow * * [MS-RDPBCGR] allows for a max desktop size of * CLIENT_MONITOR_DATA_MAXIMUM_VIRTUAL_DESKTOP_WIDTH x * CLIENT_MONITOR_DATA_MAXIMUM_VIRTUAL_DESKTOP_HEIGHT * Each pixel needs up to 4 bytes */ static int framebuffer_update_size_ok(int cx, int cy) { return (cx <= CLIENT_MONITOR_DATA_MAXIMUM_VIRTUAL_DESKTOP_WIDTH && cy <= CLIENT_MONITOR_DATA_MAXIMUM_VIRTUAL_DESKTOP_HEIGHT && (cx * cy) <= (INT_MAX / 4)); } /**************************************************************************//** * Parses an entire framebuffer update message from the wire, and returns the * first matching ExtendedDesktopSize encoding if found. * * Caller can check for a match by examining match_layout.count after the call * * @param v VNC object * @param match Function to call to check for a match * @param [out] match_x Matching x parameter for an encoding (if needed) * @param [out] match_y Matching y parameter for an encoding (if needed) * @param [out] match_layout Returned layout for the encoding * @return != 0 for error */ static int find_matching_extended_rect(struct vnc *v, int (*match)(int x, int y, int cx, int cy), int *match_x, int *match_y, struct vnc_screen_layout *match_layout) { int error; struct stream *s; unsigned int num_rects; unsigned int i; int x; int y; int cx; int cy; encoding_type encoding; int found = 0; make_stream(s); init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 3); if (error == 0) { in_uint8s(s, 1); in_uint16_be(s, num_rects); for (i = 0; i < num_rects; ++i) { if (error != 0) { break; } init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 12); if (error == 0) { in_uint16_be(s, x); in_uint16_be(s, y); in_uint16_be(s, cx); in_uint16_be(s, cy); in_uint32_be(s, encoding); if (!framebuffer_update_size_ok(cx, cy)) { LOG(LOG_LEVEL_ERROR, "find_matching_extended_rect: Frame buffer too large"); error = 1; } else if (encoding == RFB_ENC_EXTENDED_DESKTOP_SIZE && !found && match(x, y, cx, cy)) { LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: VNC matched ExtendedDesktopSize rectangle " "x=%d, y=%d geom=%dx%d", x, y, cx, cy); found = 1; error = read_extended_desktop_size_rect(v, match_layout); if (match_x) { *match_x = x; } if (match_y) { *match_y = y; } match_layout->total_width = cx; match_layout->total_height = cy; } else { error = skip_encoding(v, x, y, cx, cy, encoding); } } } } free_stream(s); return error; } /**************************************************************************//** * Sends a FramebufferUpdateRequest for the resize status state machine * * The state machine is used at the start of the connection to negotiate * a common geometry between the client and the server. * * The RFB community wiki contains the following paragraph not present * in RFC6143:- * * Note that an empty area can still solicit a FramebufferUpdate * even though that update will only contain pseudo-encodings * * This doesn't seem to be as widely supported as we would like at * present. We will always request at least a single pixel update to * avoid confusing the server. * * @param v VNC object * @return != 0 for error */ static int send_update_request_for_resize_status(struct vnc *v) { int error = 0; struct stream *s; make_stream(s); init_stream(s, 8192); switch (v->resize_status) { case VRS_WAITING_FOR_FIRST_UPDATE: case VRS_WAITING_FOR_RESIZE_CONFIRM: /* * Ask for an immediate, minimal update. */ out_uint8(s, RFB_C2S_FRAMEBUFFER_UPDATE_REQUEST); out_uint8(s, 0); /* incremental == 0 : Full update */ out_uint16_be(s, 0); out_uint16_be(s, 0); out_uint16_be(s, 1); out_uint16_be(s, 1); s_mark_end(s); error = lib_send_copy(v, s); break; default: /* * Ask for a full update from the server */ if (v->suppress_output == 0) { out_uint8(s, RFB_C2S_FRAMEBUFFER_UPDATE_REQUEST); out_uint8(s, 0); /* incremental == 0 : Full update */ out_uint16_be(s, 0); out_uint16_be(s, 0); out_uint16_be(s, v->server_layout.total_width); out_uint16_be(s, v->server_layout.total_height); s_mark_end(s); error = lib_send_copy(v, s); } break; } free_stream(s); return error; } /**************************************************************************//** * Tests if extended desktop size rect is an initial geometry specification * * This should be x == 0, but the specification says to treat undefined * values as 0 also */ static int rect_is_initial_geometry(int x, int y, int cx, int cy) { return (x != 1 && x != 2); } /**************************************************************************//** * Tests if extended desktop size rect is a reply to a request from us */ static int rect_is_reply_to_us(int x, int y, int cx, int cy) { return (x == 1); } /**************************************************************************//** * Tests if extended desktop size rect is a general change. * * This happens when we are looking for a layout change that the * VNC server has reported as forwarded to the real desktop. */ static int rect_is_general_change(int x, int y, int cx, int cy) { return (x == 0); } /**************************************************************************//** * Handles the first framebuffer update from the server * * This is used to determine if the server supports resizes from * us. See The RFB community wiki for details. * * If the server does support resizing, we send our client geometry over. * * @param v VNC object * @return != 0 for error */ static int lib_framebuffer_first_update(struct vnc *v) { int error; struct vnc_screen_layout layout = {0}; error = find_matching_extended_rect(v, rect_is_initial_geometry, NULL, NULL, &layout); if (error == 0) { if (layout.count > 0) { LOG(LOG_LEVEL_DEBUG, "VNC server supports resizing"); v->resize_supported = VRSS_SUPPORTED; v->server_layout = layout; /* Force the client geometry over to the server */ log_screen_layout(LOG_LEVEL_INFO, "ClientLayout", &v->client_layout); log_screen_layout(LOG_LEVEL_INFO, "OldServerLayout", &layout); /* * If we've only got one screen, and the other side has * only got one screen, we will preserve their screen ID * and any flags. This may prevent us sending an unwanted * SetDesktopSize message if the screen dimensions are * a match. We can't do this with more than one screen, * as we have no way to map different IDs */ if (layout.count == 1 && v->client_layout.count == 1) { LOG(LOG_LEVEL_DEBUG, "VNC " "setting screen id to %d from server", layout.s[0].id); v->client_layout.s[0].id = layout.s[0].id; v->client_layout.s[0].flags = layout.s[0].flags; } resize_server_to_client_layout(v); } else { LOG(LOG_LEVEL_DEBUG, "VNC server does not support resizing"); v->resize_supported = VRSS_NOT_SUPPORTED; /* Force client to same size as server */ LOG(LOG_LEVEL_DEBUG, "Resizing client to server %dx%d", v->server_layout.total_width, v->server_layout.total_height); error = resize_client_to_server(v, 0); v->resize_status = VRS_DONE; } } if (error == 0) { error = send_update_request_for_resize_status(v); } return error; } /**************************************************************************//** * Looks for a resize confirm in a framebuffer update request * * If the server supports resizes from us, this is used to find the * reply to our resize request. See The RFB community wiki for details. * * @param v VNC object * @return != 0 for error */ static int lib_framebuffer_waiting_for_resize_confirm(struct vnc *v) { int error; struct vnc_screen_layout layout = {0}; int response_code = 0; error = find_matching_extended_rect(v, rect_is_reply_to_us, NULL, &response_code, &layout); if (error == 0) { if (layout.count > 0) { if (response_code == RFB_EDS_REQUEST_FORWARDED) { LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: VNC server resize forwarded"); log_screen_layout(LOG_LEVEL_INFO, "ForwardedLayout", &layout); v->forward_timer = timers_oneshot_init(FORWARDED_RESIZE_TIMEOUT); v->forwarded_layout = layout; } else { // The resize has either succeeded or failed if (response_code == RFB_EDS_NO_ERROR) { LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE:" " VNC server successfully resized"); log_screen_layout(LOG_LEVEL_INFO, "NewLayout", &layout); v->server_layout = layout; } else { LOG(LOG_LEVEL_WARNING, "VNC server resize failed - error code %d [%s]", response_code, rfb_get_eds_status_msg(response_code)); // This is awkward. The client has asked for a // specific size which we can't support. // // Currently we handle this by queueing a resize // to our supported size, and continuing with the // resize state machine in xrdp_mm.c LOG(LOG_LEVEL_WARNING, "Resizing client to server"); error = resize_client_to_server(v, 0); } v->resize_status = VRS_DONE; if (error == 0) { // If this resize was requested by the client mid-session // (dynamic resize), we need to tell xrdp_mm that // it's OK to continue with the resize state machine. error = v->server_monitor_resize_done(v); if (error == 0) { error = send_update_request_for_resize_status(v); } } } } } return error; } /**************************************************************************//** * Looks for the forwarded screen layout in a framebuffer update request * * Looks for an ExtendedDesktopSize rectangle following a notification * from the VNC server that the request has been forwarded to the real * desktop. See rfbproto/pfbproto#32 for more info. * * @param v VNC object * @return != 0 for error */ static int lib_framebuffer_look_for_forwarded_layout(struct vnc *v) { int error; struct vnc_screen_layout layout = {0}; int x = 0; int y = 0; error = find_matching_extended_rect(v, rect_is_general_change, &x, &y, &layout); if (error == 0) { if (layout.count > 0) { if (vnc_screen_layouts_equal(&layout, &v->forwarded_layout)) { LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: VNC server forwarded resize complete"); free(v->forward_timer); v->forward_timer = NULL; v->server_layout = layout; error = v->server_monitor_resize_done(v); v->resize_status = VRS_DONE; } else { LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: Ignored ExtendedDesktopSize %dx%d x=%d y=%d", layout.total_width, layout.total_height, x, y); // Delay for a little before we send another request for // the size g_sleep(100); } if (error == 0) { error = send_update_request_for_resize_status(v); } } } return error; } /******************************************************************************/ /* * The VNC server has not actioned a forwarded resize request */ static int forward_timer_expired(struct vnc *v) { LOG(LOG_LEVEL_WARNING, "VNC server forwarded resize timed out"); LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: VNC server forwarded resize timed out"); free(v->forward_timer); v->forward_timer = NULL; v->resize_status = VRS_DONE; int rv = v->server_monitor_resize_done(v); if (rv == 0) { LOG(LOG_LEVEL_WARNING, "Resizing client to server"); rv = resize_client_to_server(v, 0); } return rv; } /******************************************************************************/ static int lib_framebuffer_update(struct vnc *v) { char *d1; char *d2; char cursor_data[32 * (32 * 3)]; char cursor_mask[32 * (32 / 8)]; char text[256]; int num_recs; int i; int j; int k; int x; int y; int cx; int cy; int srcx; int srcy; unsigned int encoding; int pixel; int r = 0; int g = 0; int b = 0; int error; int need_size; struct stream *s; struct stream *pixel_s; struct vnc_screen_layout layout = { 0 }; num_recs = 0; make_stream(pixel_s); make_stream(s); init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 3); if (error == 0) { in_uint8s(s, 1); in_uint16_be(s, num_recs); error = v->server_begin_update(v); } for (i = 0; i < num_recs; i++) { if (error != 0) { break; } init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 12); if (error == 0) { in_uint16_be(s, x); in_uint16_be(s, y); in_uint16_be(s, cx); in_uint16_be(s, cy); in_uint32_be(s, encoding); if (!framebuffer_update_size_ok(cx, cy)) { LOG(LOG_LEVEL_ERROR, "lib_framebuffer_update: Frame buffer too large"); error = 1; } else if (encoding == RFB_ENC_RAW) { need_size = cx * cy * get_bytes_per_pixel(v->server_bpp); init_stream(pixel_s, need_size); error = trans_force_read_s(v->trans, pixel_s, need_size); if (error == 0) { error = v->server_paint_rect(v, x, y, cx, cy, pixel_s->data, cx, cy, 0, 0); } } else if (encoding == RFB_ENC_COPY_RECT) { init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 4); if (error == 0) { in_uint16_be(s, srcx); in_uint16_be(s, srcy); error = v->server_screen_blt(v, x, y, cx, cy, srcx, srcy); } } else if (encoding == RFB_ENC_CURSOR) { g_memset(cursor_data, 0, 32 * (32 * 3)); g_memset(cursor_mask, 0, 32 * (32 / 8)); j = cx * cy * get_bytes_per_pixel(v->server_bpp); k = ((cx + 7) / 8) * cy; init_stream(s, j + k); error = trans_force_read_s(v->trans, s, j + k); if (error == 0) { in_uint8p(s, d1, j); in_uint8p(s, d2, k); for (j = 0; j < 32; j++) { for (k = 0; k < 32; k++) { pixel = get_pixel_safe(d2, k, 31 - j, cx, cy, 1); set_pixel_safe(cursor_mask, k, j, 32, 32, 1, !pixel); if (pixel) { pixel = get_pixel_safe(d1, k, 31 - j, cx, cy, v->server_bpp); split_color(pixel, &r, &g, &b, v->server_bpp, v->palette); pixel = make_color(r, g, b, 24); set_pixel_safe(cursor_data, k, j, 32, 32, 24, pixel); } } } /* keep these in 32x32, vnc cursor can be a lot bigger */ if (x > 31) { x = 31; } if (y > 31) { y = 31; } error = v->server_set_cursor(v, x, y, cursor_data, cursor_mask); } } else if (encoding == RFB_ENC_DESKTOP_SIZE) { /* Server end has resized */ init_single_screen_layout(cx, cy, &v->server_layout); error = resize_client_to_server(v, 1); } else if (encoding == RFB_ENC_EXTENDED_DESKTOP_SIZE) { layout.total_width = cx; layout.total_height = cy; error = read_extended_desktop_size_rect(v, &layout); /* If this is a reply to a request from us, x == 1 */ LOG(LOG_LEVEL_DEBUG, "VNC_RESIZE: Read ExtendedDesktopSize %dx%d x=%d y=%d", layout.total_width, layout.total_height, x, y); if (error == 0 && x != 1) { if (!vnc_screen_layouts_equal(&v->server_layout, &layout)) { v->server_layout = layout; log_screen_layout(LOG_LEVEL_INFO, "NewServerLayout", &v->server_layout); error = resize_client_to_server(v, 1); } } } else { g_sprintf(text, "VNC error in lib_framebuffer_update encoding = %8.8x", encoding); v->server_msg(v, text, 1); } } } if (error == 0) { error = v->server_end_update(v); } if (error == 0) { if (v->suppress_output == 0) { init_stream(s, 8192); out_uint8(s, RFB_C2S_FRAMEBUFFER_UPDATE_REQUEST); out_uint8(s, 1); /* incremental == 1 : Changes only */ out_uint16_be(s, 0); out_uint16_be(s, 0); out_uint16_be(s, v->server_layout.total_width); out_uint16_be(s, v->server_layout.total_height); s_mark_end(s); error = lib_send_copy(v, s); } } free_stream(s); free_stream(pixel_s); return error; } /******************************************************************************/ static int lib_palette_update(struct vnc *v) { struct stream *s; int first_color; int num_colors; int i; int r; int g; int b; int error; make_stream(s); init_stream(s, 8192); error = trans_force_read_s(v->trans, s, 5); if (error == 0) { in_uint8s(s, 1); in_uint16_be(s, first_color); in_uint16_be(s, num_colors); if ((first_color + num_colors) > VNC_PALETTE_SIZE) { LOG(LOG_LEVEL_ERROR, "lib_palette_update: palette overflow"); free_stream(s); return 1; } init_stream(s, 8192); error = trans_force_read_s(v->trans, s, num_colors * 6); } if (error == 0) { for (i = 0; i < num_colors; i++) { in_uint16_be(s, r); in_uint16_be(s, g); in_uint16_be(s, b); r = r >> 8; g = g >> 8; b = b >> 8; v->palette[first_color + i] = (r << 16) | (g << 8) | b; } error = v->server_begin_update(v); } if (error == 0) { error = v->server_palette(v, v->palette); } if (error == 0) { error = v->server_end_update(v); } free_stream(s); return error; } /******************************************************************************/ static int lib_bell_trigger(struct vnc *v) { int error; error = v->server_bell_trigger(v); return error; } /******************************************************************************/ static int lib_mod_signal(struct vnc *v) { return 0; } /******************************************************************************/ static int lib_mod_process_message(struct vnc *v, struct stream *s) { char type; int error; char text[256]; in_uint8(s, type); error = 0; if (error == 0) { if (type == RFB_S2C_FRAMEBUFFER_UPDATE) { switch (v->resize_status) { case VRS_WAITING_FOR_FIRST_UPDATE: error = lib_framebuffer_first_update(v); break; case VRS_WAITING_FOR_RESIZE_CONFIRM: if (v->forward_timer != NULL) { error = lib_framebuffer_look_for_forwarded_layout(v); } else { error = lib_framebuffer_waiting_for_resize_confirm(v); } break; default: error = lib_framebuffer_update(v); } } else if (type == RFB_S2C_SET_COLOUR_MAP_ENTRIES) { error = lib_palette_update(v); } else if (type == RFB_S2C_BELL) { error = lib_bell_trigger(v); } else if (type == RFB_S2C_SERVER_CUT_TEXT) /* clipboard */ { LOG(LOG_LEVEL_DEBUG, "VNC got clip data"); error = vnc_clip_process_rfb_data(v); } else { g_sprintf(text, "VNC unknown in lib_mod_process_message %d", type); v->server_msg(v, text, 1); } } return error; } /******************************************************************************/ static int lib_mod_start(struct vnc *v, int w, int h, int bpp) { v->server_begin_update(v); v->server_set_fgcolor(v, 0); v->server_fill_rect(v, 0, 0, w, h); v->server_end_update(v); v->server_bpp = bpp; return 0; } /******************************************************************************/ static int lib_data_in(struct trans *trans) { struct vnc *self; struct stream *s; LOG_DEVEL(LOG_LEVEL_TRACE, "lib_data_in:"); if (trans == 0) { return 1; } self = (struct vnc *)(trans->callback_data); s = trans_get_in_s(trans); if (s == 0) { return 1; } if (lib_mod_process_message(self, s) != 0) { LOG(LOG_LEVEL_ERROR, "lib_data_in: lib_mod_process_message failed"); return 1; } init_stream(s, 0); return 0; } /******************************************************************************/ /** * Gets a reason string from the server * * Sometimes the server sends an error, which is a 32-bit word * followed by a string */ static int get_reason_string(struct vnc *v, char buff[], unsigned int bufflen) { int rv = 1; struct stream *s; make_stream(s); init_stream(s, (int)(bufflen + 4)); if (trans_force_read_s(v->trans, s, 4) == 0) { unsigned int len; in_uint32_be(s, len); if (len < bufflen) { if (trans_force_read_s(v->trans, s, len) == 0) { in_uint8a(s, buff, len); buff[len] = '\0'; rv = 0; } } } free_stream(s); return rv; } /******************************************************************************/ /** * Negotiates the protocol version with the server * * @param v Module * @param[out] next_char Either security-type (version 3.3) or * number-of-security-types (versions > 3.3) * @return 0 for error, or protocol version > 0 * * The protocol negotiation is overlapped with the security negotiation. * The result of the protocol negotiation is either good, in which case * 'next_char' contains a protocol-dependent value, or not-good, in which * case 'next_char' was set to zero and followed by a reason string. The * reason string is consumed here, and logged. * * See sections 7.1.1 and 7.1.2 of the RFB community wiki */ static unsigned int negotiate_protocol_version(struct vnc *v, unsigned char *next_char) { struct stream *s = NULL; unsigned int major; unsigned int minor; unsigned int version; int sec_type_size; make_stream(s); init_stream(s, 64); if (trans_force_read_s(v->trans, s, 12) != 0) { LOG(LOG_LEVEL_ERROR, "Error reading server version string"); goto fail; } const char *p; in_uint8p(s, p, 12); /* Moves s->p to end of string */ /* Expecting a string "RFB ???.???\n" where ? is a digit */ if (*p++ != 'R' || *p++ != 'F' || *p++ != 'B' || *p++ != ' ' || !isdigit(*p++) || !isdigit(*p++) || !isdigit(*p++) || *p++ != '.' || !isdigit(*p++) || !isdigit(*p++) || !isdigit(*p++) || *p++ != '\n') { LOG_HEXDUMP(LOG_LEVEL_ERROR, "Invalid RFB string :", s->data, 12); goto fail; } /* Parse the major/minor versions in-place */ p = s->data; major = (p[4] - '0') * 100 + (p[5] - '0') * 10 + (p[6] - '0'); minor = (p[8] - '0') * 100 + (p[9] - '0') * 10 + (p[10] - '0'); version = MAKE_RFBPROTO_VER(major, minor); if (version == RFBPROTO_VER_3_3 || version == RFBPROTO_VER_3_7 || version == RFBPROTO_VER_3_8) { /* Versions documented in RFC6143 */ LOG(LOG_LEVEL_INFO, "RFB version %d.%d is supported by VNC server", major, minor); } else if (major == 3) { /* RFC6143 section 6 states that unknown 3.x versions should * be treated as 3.3 */ LOG(LOG_LEVEL_INFO, "RFB server reports version %d.%d.", major, minor); minor = 3; version = MAKE_RFBPROTO_VER(major, minor); LOG(LOG_LEVEL_INFO, "Proposing RFB version %d.%d to server", major, minor); } else if (major > 3) { /* This must be a new server version. Try to fall back to 3.8 */ LOG(LOG_LEVEL_INFO, "RFB server reports version %d.%d.", major, minor); major = 3; minor = 8; version = MAKE_RFBPROTO_VER(major, minor); LOG(LOG_LEVEL_INFO, "Proposing RFB version %d.%d to server", major, minor); } else { LOG(LOG_LEVEL_ERROR, "Server reports unsupported RFB version %d.%d", major, minor); goto fail; } /* Send our proposed version back to the server */ /* s->p should be in the right place to mark the end * of the string */ g_snprintf(s->data, s->size, "RFB %03d.%03d\n", major, minor); s_mark_end(s); if (trans_force_write_s(v->trans, s) != 0) { LOG(LOG_LEVEL_ERROR, "Error writing server version string"); goto fail; } // Version 3.3 sends back a U32 rather than a U8 for the security // type, even though the values it supports fit easily in a U8 sec_type_size = (version == RFBPROTO_VER_3_3) ? 4 : 1; init_stream(s, 64); if (trans_force_read_s(v->trans, s, sec_type_size) != 0) { LOG(LOG_LEVEL_ERROR, "Can't read negotiation result from server"); goto fail; } // Read the next field, being careful to parse a whole U32 for // version 3.3. if (version == RFBPROTO_VER_3_3) { in_uint8s(s, 3); // Skip the three high octets } in_uint8(s, *next_char); if (*next_char == 0) { char text[256]; // Server reported a reason for failure if (get_reason_string(v, text, sizeof(text)) != 0) { g_snprintf(text, sizeof(text), "No reason given"); } LOG(LOG_LEVEL_ERROR, "Version negotiation with server failed [%s]", text); goto fail; } free_stream(s); return version; fail: free_stream(s); return 0; } /******************************************************************************/ /** * Chooses the security type from a list sent by the server * * @param v Module * @param max_security_type Max security type to negotiate * @return 0 for error, or security type ( > 0) * * See section 7.1.2 of the RFB community wiki * * This call is only made for RFB version 3.7 onwards */ static enum sec_type choose_security_type(struct vnc *v, unsigned char number_of_security_types, enum sec_type max_security_type) { enum sec_type sec_type = SEC_TYPE_INVALID; struct stream *s = NULL; make_stream(s); init_stream(s, UCHAR_MAX); if (trans_force_read_s(v->trans, s, number_of_security_types) != 0) { LOG(LOG_LEVEL_ERROR, "Can't read list of security types from server"); } else { while (s_rem(s) > 0) { int j; in_uint8(s, j); enum sec_type st = (enum sec_type)j; // Choose the highest security level that we support if (st > max_security_type) { // If in development, log unsupported security types LOG_DEVEL(LOG_LEVEL_INFO, "Unsupported VNC security type %d was offered", j); continue; } if (st > sec_type) { sec_type = st; } } if (sec_type == SEC_TYPE_INVALID) { LOG(LOG_LEVEL_ERROR, "RFB server did not offer a compatible security type"); } else { init_stream(s, 1); out_uint8(s, sec_type); s_mark_end(s); if (trans_force_write_s(v->trans, s) != 0) { LOG(LOG_LEVEL_ERROR, "Can't send security type to server"); sec_type = SEC_TYPE_INVALID; } } } free_stream(s); return sec_type; } /******************************************************************************/ /** * Negotiates the security type with the server * * @param v Module * @param rfbproto_version RFB version negotiated with the server * @param next_char The character sent back (>0) when the protocol * version was agreed. * @return the negotiated security type (or SEC_TYPE_INVALID) */ static enum sec_type negotiate_security_type(struct vnc *v, unsigned int rfbproto_version, unsigned char next_char) { char text[256]; enum sec_type sec_type; // Whether the SecurityResult word is read from the server // (RFB Community wiki section 7.1.3) int check_sec_result = 1; struct stream *s = NULL; make_stream(s); init_stream(s, 64); if (rfbproto_version == RFBPROTO_VER_3_3) { // The server has already chosen the security type sec_type = (enum sec_type)next_char; } else { // The client chooses the security type based on what's offered sec_type = choose_security_type(v, next_char, SEC_TYPE_MAX); } if (sec_type == SEC_TYPE_INVALID) { goto fail; // An error has already been logged } g_sprintf(text, "VNC security type is %s", SEC_TYPE_TO_STR(sec_type)); v->server_msg(v, text, 0); switch (sec_type) { case SEC_TYPE_NONE: if (rfbproto_version < RFBPROTO_VER_3_8) { check_sec_result = 0; } break; case SEC_TYPE_VNC_AUTH: { init_stream(s, 64); if (trans_force_read_s(v->trans, s, 16) != 0) { LOG(LOG_LEVEL_ERROR, "Can't read VNC auth challenge from server"); goto fail; } if (guid_is_set(&v->guid)) { char guid_str[GUID_STR_SIZE]; guid_to_str(&v->guid, guid_str); rfbHashEncryptBytes(s->data, guid_str); } else { rfbEncryptBytes(s->data, v->password); } s->p += 16; s_mark_end(s); if (trans_force_write_s(v->trans, s) != 0) { LOG(LOG_LEVEL_ERROR, "Can't send VNC auth response to server"); goto fail; } break; } default: // Shouldn't get here LOG(LOG_LEVEL_ERROR, "VNC unsupported security type %d", sec_type); goto fail; } if (check_sec_result) { /* RFB Community wiki 7.1.3 */ init_stream(s, 4); if (trans_force_read_s(v->trans, s, 4) != 0) { LOG(LOG_LEVEL_ERROR, "Can't read SecurityResult from server"); goto fail; } int i; in_uint32_be(s, i); if (i != 0) { char msg[256]; // Versions >= 3.8 of the protocol send a reason string at // this point if (rfbproto_version < RFBPROTO_VER_3_8 || get_reason_string(v, text, sizeof(text)) != 0) { g_snprintf(text, sizeof(text), "No reason given"); } g_snprintf(msg, sizeof(msg), "VNC security negotiation failed [%s]", text); v->server_msg(v, msg, 0); goto fail; } } free_stream(s); return sec_type; fail: free_stream(s); return SEC_TYPE_INVALID; } /******************************************************************************/ /** * Sends the client init to the server (RFC6143 7.3.1) * * @param v Module * @param share_flag Share flag value to send to the server * @return 0 for success */ static int send_client_init(struct vnc *v, int share_flag) { int rv; struct stream *s; make_stream(s); init_stream(s, 64); out_uint8(s, (share_flag) ? 1 : 0); s_mark_end(s); rv = trans_force_write_s(v->trans, s); free_stream(s); return rv; } /******************************************************************************/ /** * Receives the server init from the server (RFC6143 7.3.2) * * @param v Module * @return 0 for success */ static int receive_server_init(struct vnc *v) { int rv; struct stream *s; int width; int height; int name_len; make_stream(s); init_stream(s, 256); rv = trans_force_read_s(v->trans, s, 2 + 2 + 16 + 4); if (rv == 0) { in_uint16_be(s, width); in_uint16_be(s, height); in_uint8s(s, 16); // skip server pixel format in_uint32_be(s, name_len); init_stream(s, 256); // Reset stream to read name if (name_len > 255 || name_len < 0) { LOG(LOG_LEVEL_ERROR, "Unexpected name length %d received", name_len); rv = 1; } else if (trans_force_read_s(v->trans, s, name_len) != 0) { LOG(LOG_LEVEL_ERROR, "Error receiving desktop name"); rv = 1; } else { g_memcpy(v->mod_name, s->data, name_len); v->mod_name[name_len] = 0; init_single_screen_layout(width, height, &v->server_layout); } } free_stream(s); return rv; } /******************************************************************************/ /** * Sets the pixel format (RFC6143 7.5.1) * * @param v Module * @return 0 for success */ static int set_pixel_format(struct vnc *v) { struct { unsigned char bits_per_pixel; unsigned char depth; unsigned char true_color; unsigned short red_max; unsigned short green_max; unsigned short blue_max; unsigned char red_shift; unsigned char green_shift; unsigned char blue_shift; } pixel_format = {0}; int rv; struct stream *s; if (v->server_bpp == 8) { pixel_format.bits_per_pixel = 8; pixel_format.depth = 8; } else if (v->server_bpp == 15) { pixel_format.bits_per_pixel = 16; pixel_format.depth = 15; pixel_format.true_color = 1; pixel_format.red_max = 31; pixel_format.green_max = 31; pixel_format.blue_max = 31; pixel_format.red_shift = 10; pixel_format.green_shift = 5; pixel_format.blue_shift = 0; } else if (v->server_bpp == 16) { pixel_format.bits_per_pixel = 16; pixel_format.depth = 16; pixel_format.true_color = 1; pixel_format.red_max = 31; pixel_format.green_max = 63; pixel_format.blue_max = 31; pixel_format.red_shift = 11; pixel_format.green_shift = 5; pixel_format.blue_shift = 0; } else if (v->server_bpp == 24 || v->server_bpp == 32) { pixel_format.bits_per_pixel = 32; pixel_format.depth = 24; pixel_format.true_color = 1; pixel_format.red_max = 255; pixel_format.green_max = 255; pixel_format.blue_max = 255; pixel_format.red_shift = 16; pixel_format.green_shift = 8; pixel_format.blue_shift = 0; } make_stream(s); init_stream(s, 64); out_uint8(s, RFB_C2S_SET_PIXEL_FORMAT); out_uint8s(s, 3); /* pad */ /* Now send the pixel data block */ out_uint8(s, pixel_format.bits_per_pixel); out_uint8(s, pixel_format.depth); #if defined(B_ENDIAN) out_uint8(s, 1); /* big endian */ #else out_uint8(s, 0); /* big endian */ #endif out_uint8(s, pixel_format.true_color); out_uint16_be(s, pixel_format.red_max); out_uint16_be(s, pixel_format.green_max); out_uint16_be(s, pixel_format.blue_max); out_uint8(s, pixel_format.red_shift); out_uint8(s, pixel_format.green_shift); out_uint8(s, pixel_format.blue_shift); out_uint8s(s, 3); /* pad */ s_mark_end(s); rv = trans_force_write_s(v->trans, s); free_stream(s); return rv; } /******************************************************************************/ /** * Sets the encodings (RFC6143 7.5.2) * * @param v Module * @return 0 for success */ static int set_encodings(struct vnc *v) { encoding_type e[10]; unsigned int n = 0; unsigned int i; int rv; struct stream *s; /* These encodings are always supported */ e[n++] = RFB_ENC_RAW; e[n++] = RFB_ENC_COPY_RECT; e[n++] = RFB_ENC_CURSOR; e[n++] = RFB_ENC_DESKTOP_SIZE; if (v->enabled_encodings_mask & MSK_EXTENDED_DESKTOP_SIZE) { e[n++] = RFB_ENC_EXTENDED_DESKTOP_SIZE; } else { LOG(LOG_LEVEL_INFO, "VNC User disabled EXTENDED_DESKTOP_SIZE"); } make_stream(s); init_stream(s, (int)(4 + sizeof(e))); out_uint8(s, RFB_C2S_SET_ENCODINGS); out_uint8(s, 0); out_uint16_be(s, n); /* Number of encodings following */ for (i = 0 ; i < n; ++i) { out_uint32_be(s, e[i]); } s_mark_end(s); rv = trans_force_write_s(v->trans, s); free_stream(s); return rv; } /******************************************************************************/ /* return error */ static int lib_mod_connect(struct vnc *v, int fd) { char cursor_data[32 * (32 * 3)]; char cursor_mask[32 * (32 / 8)]; char con_port[256]; char text[256]; unsigned int rfbproto_version; int error; int socket_mode; g_snprintf(con_port, sizeof(con_port), "%s", v->port); /* check if bpp is supported for rdp connection */ switch (v->server_bpp) { case 8: case 15: case 16: case 24: case 32: break; default: v->server_msg(v, "VNC error - only supporting 8, 15, 16, 24 and 32 " "bpp rdp connections", 0); return 1; } /* Assume a TCP-port based connection (i.e. not a UDS connection) * if the port is not an absolute path */ if (con_port[0] == '/') { socket_mode = TRANS_MODE_UNIX; } else { socket_mode = TRANS_MODE_TCP; if (g_strcmp(v->ip, "") == 0) { v->server_msg(v, "VNC error - no IP set for TCP connection", 0); goto fail; } } v->trans = trans_create(socket_mode, 8 * 8192, 8192); if (v->trans == 0) { v->server_msg(v, "VNC error: trans_create() failed", 0); goto fail; } v->sck_closed = 0; if (v->delay_ms > 0) { g_sprintf(text, "Waiting %d ms for VNC to start...", v->delay_ms); v->server_msg(v, text, 0); g_sleep(v->delay_ms); } v->trans->si = v->si; v->trans->my_source = XRDP_SOURCE_MOD; if (fd >= 0) { v->trans->sck = fd; v->trans->status = TRANS_STATUS_UP; /* ok */ v->trans->type1 = TRANS_TYPE_CLIENT; /* client */ error = 0; } else { error = trans_connect(v->trans, v->ip, con_port, 3000); } if (error != 0) { g_snprintf(text, sizeof(text), "Error connecting to VNC server [%s]", g_get_strerror()); v->server_msg(v, text, 0); goto fail; } if (socket_mode == TRANS_MODE_TCP) { g_sprintf(text, "VNC connected to TCP %s %s", v->ip, con_port); } else { g_sprintf(text, "VNC connected to local socket %s", con_port); } v->server_msg(v, text, 0); /* protocol version */ unsigned char next_char; if ((rfbproto_version = negotiate_protocol_version(v, &next_char)) == 0) { v->server_msg(v, "Error negotiating VNC version", 0); goto fail; } if (negotiate_security_type(v, rfbproto_version, next_char) == SEC_TYPE_INVALID) { // An error has been logged v->server_msg(v, "Error negotiating security type", 0); goto fail; } if (send_client_init(v, 1) != 0) { v->server_msg(v, "Error sending client init", 0); goto fail; } if (receive_server_init(v) != 0) { v->server_msg(v, "Error receiving server init", 0); goto fail; } if (set_pixel_format(v) != 0) { v->server_msg(v, "Error setting pixel format", 0); goto fail; } if (set_encodings(v) != 0) { v->server_msg(v, "Error setting encodings", 0); goto fail; } v->resize_supported = VRSS_UNKNOWN; v->resize_status = VRS_WAITING_FOR_FIRST_UPDATE; if (send_update_request_for_resize_status(v) != 0) { v->server_msg(v, "Error sending resize support request", 0); goto fail; } /* set almost null cursor, this is the little dot cursor */ g_memset(cursor_data, 0, 32 * (32 * 3)); g_memset(cursor_data + (32 * (32 * 3) - 1 * 32 * 3), 0xff, 9); g_memset(cursor_data + (32 * (32 * 3) - 2 * 32 * 3), 0xff, 9); g_memset(cursor_data + (32 * (32 * 3) - 3 * 32 * 3), 0xff, 9); g_memset(cursor_mask, 0xff, 32 * (32 / 8)); if (v->server_set_cursor(v, 3, 3, cursor_data, cursor_mask) != 0) { v->server_msg(v, "Error sending cursor", 0); goto fail; } v->server_msg(v, "VNC connection complete, connected ok", 0); vnc_clip_open_clip_channel(v); v->trans->trans_data_in = lib_data_in; v->trans->header_size = 1; v->trans->callback_data = v; return 0; fail: trans_delete(v->trans); v->trans = NULL; v->server_msg(v, "VNC error - problem connecting", 0); return 1; } /******************************************************************************/ static int lib_mod_end(struct vnc *v) { if (v->vnc_desktop != 0) { } return 0; } /**************************************************************************//** * Initialises the client layout from the Windows monitor definition. * * @param v VNC module * @param [in] width session width * @param [in] height session height * @param [in] num_monitors (can be 0, meaning one monitor) * @param [in] monitors Monitor definitions for num_monitors > 0 */ static void init_client_layout(struct vnc *v, int width, int height, int num_monitors, const struct monitor_info *monitors) { struct vnc_screen_layout *layout = &v->client_layout; if (!v->multimon_configured || num_monitors < 1) { init_single_screen_layout(width, height, layout); } else { layout->total_width = width; layout->total_height = height; layout->count = num_monitors; unsigned int i; for (i = 0 ; i < layout->count; ++i) { layout->s[i].id = i; layout->s[i].x = monitors[i].left; layout->s[i].y = monitors[i].top; layout->s[i].width = monitors[i].right - monitors[i].left + 1; layout->s[i].height = monitors[i].bottom - monitors[i].top + 1; layout->s[i].flags = 0; } } } /******************************************************************************/ static int lib_mod_set_param(struct vnc *v, const char *name, const char *value) { if (g_strcasecmp(name, "username") == 0) { g_strncpy(v->username, value, 255); } else if (g_strcasecmp(name, "password") == 0) { g_strncpy(v->password, value, 255); } else if (g_strcasecmp(name, "ip") == 0) { g_strncpy(v->ip, value, 255); } else if (g_strcasecmp(name, "port") == 0) { g_strncpy(v->port, value, 255); } else if (g_strcasecmp(name, "keylayout") == 0) { v->keylayout = g_atoi(value); } else if (g_strcasecmp(name, "delay_ms") == 0) { v->delay_ms = g_atoi(value); } else if (g_strcasecmp(name, "guid") == 0) { v->guid = *(struct guid *)value; } else if (g_strcasecmp(name, "disabled_encodings_mask") == 0) { v->enabled_encodings_mask = (unsigned int)~g_atoi(value); } else if (g_strcasecmp(name, "client_info") == 0) { const struct xrdp_client_info *client_info = (const struct xrdp_client_info *) value; v->multimon_configured = client_info->multimon; /* Save monitor information from the client * Use minfo_wm, as this is normalised for a top-left of (0,0) * as required by RFC6143 */ init_client_layout(v, client_info->display_sizes.session_width, client_info->display_sizes.session_height, client_info->display_sizes.monitorCount, client_info->display_sizes.minfo_wm); log_screen_layout(LOG_LEVEL_DEBUG, "client_info", &v->client_layout); } return 0; } /******************************************************************************/ /* return error */ static int lib_mod_get_wait_objs(struct vnc *v, tbus *read_objs, int *rcount, tbus *write_objs, int *wcount, int *timeout) { LOG_DEVEL(LOG_LEVEL_TRACE, "lib_mod_get_wait_objs:"); if (v != 0) { if (v->trans != 0) { trans_get_wait_objs_rw(v->trans, read_objs, rcount, write_objs, wcount, timeout); } // Update timeout with any active timers unsigned int now = g_get_elapsed_ms(); timers_oneshot_update_poll(v->forward_timer, now, timeout); } return 0; } /******************************************************************************/ /* return error */ static int lib_mod_check_wait_objs(struct vnc *v) { int rv; rv = 0; if (v != 0) { if (v->trans != 0) { if ((rv = trans_check_wait_objs(v->trans)) != 0) { LOG(LOG_LEVEL_ERROR, "VNC server closed connection"); } else { // Check timers unsigned int now = g_get_elapsed_ms(); if (timers_oneshot_get_remaining(v->forward_timer, now) == 0) { rv = forward_timer_expired(v); } } } } return rv; } /******************************************************************************/ /* return error */ static int lib_mod_frame_ack(struct vnc *v, int flags, int frame_id) { return 0; } /******************************************************************************/ /* return error */ static int lib_mod_suppress_output(struct vnc *v, int suppress, int left, int top, int right, int bottom) { int error; struct stream *s; error = 0; v->suppress_output = suppress; if (suppress == 0) { make_stream(s); init_stream(s, 8192); out_uint8(s, RFB_C2S_FRAMEBUFFER_UPDATE_REQUEST); out_uint8(s, 0); /* incremental == 0 : Full contents */ out_uint16_be(s, 0); out_uint16_be(s, 0); out_uint16_be(s, v->server_layout.total_width); out_uint16_be(s, v->server_layout.total_height); s_mark_end(s); error = lib_send_copy(v, s); free_stream(s); } return error; } /******************************************************************************/ /* return error */ static int lib_mod_server_version_message(struct vnc *v) { return 0; } /******************************************************************************/ /* return error */ static int lib_mod_server_monitor_resize(struct vnc *v, int width, int height, int num_monitors, const struct monitor_info *monitors, int *in_progress) { int error; *in_progress = 0; init_client_layout(v, width, height, num_monitors, monitors); if ((error = resize_server_to_client_layout(v)) == 0) { // If we're waiting for a confirmation, send an update request. // According to the spec this should not be needed, but // it works around a buggy VNC server not sending an // ExtendedDesktopSize rectangle if the desktop change is // small (eg. same dimensions, but 2 monitors -> 1 monitor) if (v->resize_status == VRS_WAITING_FOR_RESIZE_CONFIRM && (error = send_update_request_for_resize_status(v)) == 0) { *in_progress = 1; } } return error; } /******************************************************************************/ /* return error */ static int lib_mod_server_monitor_full_invalidate(struct vnc *v, int param1, int param2) { return 0; } /******************************************************************************/ tintptr EXPORT_CC mod_init(void) { struct vnc *v; v = (struct vnc *)g_malloc(sizeof(struct vnc), 1); /* set client functions */ v->size = sizeof(struct vnc); v->version = CURRENT_MOD_VER; v->handle = (tintptr) v; v->mod_connect = lib_mod_connect; v->mod_start = lib_mod_start; v->mod_event = lib_mod_event; v->mod_signal = lib_mod_signal; v->mod_end = lib_mod_end; v->mod_set_param = lib_mod_set_param; v->mod_get_wait_objs = lib_mod_get_wait_objs; v->mod_check_wait_objs = lib_mod_check_wait_objs; v->mod_frame_ack = lib_mod_frame_ack; v->mod_suppress_output = lib_mod_suppress_output; v->mod_server_monitor_resize = lib_mod_server_monitor_resize; v->mod_server_monitor_full_invalidate = lib_mod_server_monitor_full_invalidate; v->mod_server_version_message = lib_mod_server_version_message; /* Member variables */ v->enabled_encodings_mask = -1; vnc_clip_init(v); return (tintptr) v; } /******************************************************************************/ int EXPORT_CC mod_exit(tintptr handle) { struct vnc *v = (struct vnc *) handle; LOG(LOG_LEVEL_TRACE, "VNC mod_exit"); if (v == 0) { return 0; } trans_delete(v->trans); vnc_clip_exit(v); g_free(v); return 0; }