xrdp/vnc/vnc.c

1580 lines
42 KiB
C
Raw Normal View History

/**
* xrdp: A Remote Desktop Protocol server.
*
2015-07-12 11:07:10 +03:00
* 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
*/
2004-11-28 07:56:58 +03:00
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
2004-11-28 07:56:58 +03:00
#include "vnc.h"
2012-05-27 19:17:39 +04:00
#include "log.h"
2015-07-12 11:07:10 +03:00
#include "trans.h"
2015-08-19 04:05:45 +03:00
#include "ssl_calls.h"
2004-11-28 07:56:58 +03:00
2015-01-23 22:33:58 +03:00
#define LLOG_LEVEL 1
#define LLOGLN(_level, _args) \
do \
{ \
if (_level < LLOG_LEVEL) \
{ \
g_write("xrdp:vnc [%10.10u]: ", g_time3()); \
g_writeln _args ; \
} \
} \
while (0)
#define AS_LOG_MESSAGE log_message
2017-03-12 19:35:00 +03:00
static int
2015-07-12 11:07:10 +03:00
lib_mod_process_message(struct vnc *v, struct stream *s);
/******************************************************************************/
2017-03-12 19:35:00 +03:00
static int
2015-07-12 11:07:10 +03:00
lib_send_copy(struct vnc *v, struct stream *s)
{
return trans_write_copy_s(v->trans, s);
}
2005-03-13 04:17:30 +03:00
/******************************************************************************/
/* 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 */
2017-03-12 19:35:00 +03:00
static void
rfbEncryptBytes(char *bytes, const char *passwd)
2005-03-13 04:17:30 +03:00
{
2015-08-19 04:05:45 +03:00
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 */
2017-03-12 19:35:00 +03:00
static void
rfbHashEncryptBytes(char *bytes, const char *passwd)
{
char passwd_hash[20];
char passwd_hash_text[40];
void *sha1;
int passwd_bytes;
2016-02-14 07:41:07 +03:00
/* create password hash from password */
passwd_bytes = g_strlen(passwd);
sha1 = ssl_sha1_info_create();
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);
2005-03-13 04:17:30 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
static int
lib_process_channel_data(struct vnc *v, int chanid, int flags, int size,
struct stream *s, int total_size)
{
int type;
int status;
int length;
2015-07-12 11:07:10 +03:00
int clip_bytes;
int index;
int format;
struct stream *out_s;
if (chanid == v->clip_chanid)
{
in_uint16_le(s, type);
in_uint16_le(s, status);
in_uint32_le(s, length);
//g_writeln("clip data type %d status %d length %d", type, status, length);
//g_hexdump(s->p, s->end - s->p);
switch (type)
2007-01-25 08:34:14 +03:00
{
case 2: /* CLIPRDR_FORMAT_ANNOUNCE */
AS_LOG_MESSAGE(LOG_LEVEL_DEBUG, "CLIPRDR_FORMAT_ANNOUNCE - "
"status %d length %d", status, length);
2015-07-12 11:07:10 +03:00
make_stream(out_s);
init_stream(out_s, 8192);
out_uint16_le(out_s, 3); // msg-type: CLIPRDR_FORMAT_ACK
out_uint16_le(out_s, 1); // msg-status-code: CLIPRDR_RESPONSE
out_uint32_le(out_s, 0); // null (?)
out_uint8s(out_s, 4); // pad
s_mark_end(out_s);
length = (int)(out_s->end - out_s->data);
v->server_send_to_channel(v, v->clip_chanid, out_s->data,
length, length, 3);
free_stream(out_s);
#if 0
// Send the CLIPRDR_DATA_REQUEST message to the cliprdr channel.
//
make_stream(out_s);
init_stream(out_s, 8192);
out_uint16_le(out_s, 4); // msg-type: CLIPRDR_DATA_REQUEST
out_uint16_le(out_s, 0); // msg-status-code: CLIPRDR_REQUEST
out_uint32_le(out_s, 4); // sizeof supported-format-list.
out_uint32_le(out_s, 1); // supported-format-list: CF_TEXT
out_uint8s(out_s, 0); // pad (garbage pad?)
s_mark_end(out_s);
length = (int)(out_s->end - out_s->data);
v->server_send_to_channel(v, v->clip_chanid, out_s->data,
length, length, 3);
free_stream(out_s);
2015-07-12 11:07:10 +03:00
#endif
break;
2015-07-12 11:07:10 +03:00
case 3: /* CLIPRDR_FORMAT_ACK */
AS_LOG_MESSAGE(LOG_LEVEL_DEBUG, "CLIPRDR_FORMAT_ACK - "
"status %d length %d", status, length);
break;
case 4: /* CLIPRDR_DATA_REQUEST */
AS_LOG_MESSAGE(LOG_LEVEL_DEBUG, "CLIPRDR_DATA_REQUEST - "
"status %d length %d", status, length);
format = 0;
if (length >= 4)
{
in_uint32_le(s, format);
}
/* only support CF_TEXT and CF_UNICODETEXT */
if ((format != 1) && (format != 13))
{
break;
}
make_stream(out_s);
init_stream(out_s, 8192);
out_uint16_le(out_s, 5);
out_uint16_le(out_s, 1);
if (format == 13) /* CF_UNICODETEXT */
{
2015-07-12 11:07:10 +03:00
out_uint32_le(out_s, v->clip_data_s->size * 2 + 2);
2015-07-12 11:07:10 +03:00
for (index = 0; index < v->clip_data_s->size; index++)
{
2015-07-12 11:07:10 +03:00
out_uint8(out_s, v->clip_data_s->data[index]);
out_uint8(out_s, 0);
}
out_uint8s(out_s, 2);
}
else if (format == 1) /* CF_TEXT */
{
2015-07-12 11:07:10 +03:00
out_uint32_le(out_s, v->clip_data_s->size + 1);
2015-07-12 11:07:10 +03:00
for (index = 0; index < v->clip_data_s->size; index++)
{
2015-07-12 11:07:10 +03:00
out_uint8(out_s, v->clip_data_s->data[index]);
}
out_uint8s(out_s, 1);
}
out_uint8s(out_s, 4); /* pad */
s_mark_end(out_s);
length = (int)(out_s->end - out_s->data);
v->server_send_to_channel(v, v->clip_chanid, out_s->data, length,
length, 3);
free_stream(out_s);
break;
case 5: /* CLIPRDR_DATA_RESPONSE */
AS_LOG_MESSAGE(LOG_LEVEL_DEBUG, "CLIPRDR_DATA_RESPONSE - "
"status %d length %d", status, length);
2015-07-12 11:07:10 +03:00
clip_bytes = MIN(length, 256);
// - Read the response data from the cliprdr channel, stream 's'.
// - Send the response data to the vnc server, stream 'out_s'.
//
make_stream(out_s);
// Send the RFB message type (CLIENT_CUT_TEXT) to the vnc server.
2015-07-12 11:07:10 +03:00
init_stream(out_s, clip_bytes + 1 + 3 + 4 + 16);
out_uint8(out_s, 6); // RFB msg type: CLIENT_CUT_TEXT
out_uint8s(out_s, 3); // padding
// Send the length of the cut-text to the vnc server.
2015-07-12 11:07:10 +03:00
out_uint32_be(out_s, clip_bytes);
// Send the cut-text (as read from 's') to the vnc server.
2015-07-12 11:07:10 +03:00
for (index = 0; index < clip_bytes; index++)
{
char cur_char = '\0';
in_uint8(s, cur_char); // text in from 's'
out_uint8(out_s, cur_char); // text out to 'out_s'
}
s_mark_end(out_s);
2015-07-12 11:07:10 +03:00
lib_send_copy(v, out_s);
free_stream(out_s);
break;
default:
{
log_message(LOG_LEVEL_DEBUG, "VNC clip information unhandled");
break;
}
2007-01-25 08:34:14 +03:00
}
}
else
{
log_message(LOG_LEVEL_DEBUG, "lib_process_channel_data: unknown chanid: "
"%d :(v->clip_chanid) %d", chanid, v->clip_chanid);
}
return 0;
2007-01-25 08:34:14 +03:00
}
2004-11-28 07:56:58 +03:00
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_mod_event(struct vnc *v, int msg, long param1, long param2,
2005-07-13 05:07:00 +04:00
long param3, long param4)
2004-11-28 07:56:58 +03:00
{
struct stream *s;
int key;
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 == 0x5555) /* 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))
{
init_stream(s, size);
out_uint8a(s, data, size);
s_mark_end(s);
s->p = s->data;
error = lib_process_channel_data(v, chanid, flags, size, s, total_size);
}
else
{
error = 1;
}
2007-01-25 08:34:14 +03:00
}
else if ((msg >= 15) && (msg <= 16)) /* key events */
2007-01-25 08:34:14 +03:00
{
key = param2;
if (key > 0)
{
if (key == 65027) /* altgr */
{
if (v->shift_state)
{
/* fix for mstsc sending left control down with altgr */
init_stream(s, 8192);
out_uint8(s, 4);
out_uint8(s, 0); /* down flag */
out_uint8s(s, 2);
out_uint32_be(s, 65507); /* left control */
2015-07-12 11:07:10 +03:00
s_mark_end(s);
lib_send_copy(v, s);
}
}
init_stream(s, 8192);
out_uint8(s, 4);
out_uint8(s, msg == 15); /* down flag */
out_uint8s(s, 2);
out_uint32_be(s, key);
2015-07-12 11:07:10 +03:00
s_mark_end(s);
error = lib_send_copy(v, s);
if (key == 65507) /* left control */
{
v->shift_state = msg == 15;
}
}
2007-01-25 08:34:14 +03:00
}
else if (msg >= 100 && msg <= 110) /* mouse events */
2004-11-28 07:56:58 +03:00
{
switch (msg)
2010-03-17 21:35:26 +03:00
{
case 100:
break; /* WM_MOUSEMOVE */
case 101:
v->mod_mouse_state &= ~1;
break; /* WM_LBUTTONUP */
case 102:
v->mod_mouse_state |= 1;
break; /* WM_LBUTTONDOWN */
case 103:
v->mod_mouse_state &= ~4;
break; /* WM_RBUTTONUP */
case 104:
v->mod_mouse_state |= 4;
break; /* WM_RBUTTONDOWN */
case 105:
v->mod_mouse_state &= ~2;
break;
case 106:
v->mod_mouse_state |= 2;
break;
case 107:
v->mod_mouse_state &= ~8;
break;
case 108:
v->mod_mouse_state |= 8;
break;
case 109:
v->mod_mouse_state &= ~16;
break;
case 110:
v->mod_mouse_state |= 16;
break;
2010-03-17 21:35:26 +03:00
}
init_stream(s, 8192);
out_uint8(s, 5);
out_uint8(s, v->mod_mouse_state);
out_uint16_be(s, param1);
out_uint16_be(s, param2);
2015-07-12 11:07:10 +03:00
s_mark_end(s);
error = lib_send_copy(v, s);
2004-11-28 07:56:58 +03:00
}
else if (msg == 200) /* invalidate */
{
2019-04-10 04:24:24 +03:00
if (v->suppress_output == 0)
{
/* FramebufferUpdateRequest */
init_stream(s, 8192);
out_uint8(s, 3);
out_uint8(s, 0);
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;
2004-11-28 07:56:58 +03:00
}
//******************************************************************************
2017-03-12 19:35:00 +03:00
int
get_pixel_safe(char *data, int x, int y, int width, int height, int bpp)
2004-11-28 07:56:58 +03:00
{
int start = 0;
int shift = 0;
2004-11-28 07:56:58 +03:00
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));
2005-02-08 06:45:30 +03:00
}
2004-11-28 07:56:58 +03:00
else
2005-02-08 06:45:30 +03:00
{
log_message(LOG_LEVEL_ERROR, "error in get_pixel_safe bpp %d", bpp);
}
return 0;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
void
set_pixel_safe(char *data, int x, int y, int width, int height, int bpp,
2005-07-13 05:07:00 +04:00
int pixel)
2004-11-28 07:56:58 +03:00
{
int start = 0;
int shift = 0;
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;
2005-02-08 06:45:30 +03:00
}
2004-11-28 07:56:58 +03:00
else
2005-02-08 06:45:30 +03:00
{
log_message(LOG_LEVEL_ERROR, "error in set_pixel_safe bpp %d", bpp);
}
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
split_color(int pixel, int *r, int *g, int *b, int bpp, int *palette)
2004-11-28 07:56:58 +03:00
{
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_message(LOG_LEVEL_ERROR, "error in split_color bpp %d", bpp);
}
return 0;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
2005-07-13 05:07:00 +04:00
make_color(int r, int g, int b, int bpp)
2004-11-28 07:56:58 +03:00
{
if (bpp == 24)
{
return (r << 16) | (g << 8) | b;
}
else
{
log_message(LOG_LEVEL_ERROR, "error in make_color bpp %d", bpp);
}
return 0;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_framebuffer_update(struct vnc *v)
2004-11-28 07:56:58 +03:00
{
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 Bpp;
int pixel;
int r;
int g;
int b;
int error;
2015-07-12 11:07:10 +03:00
int need_size;
struct stream *s;
2015-07-12 11:07:10 +03:00
struct stream *pixel_s;
num_recs = 0;
Bpp = (v->mod_bpp + 7) / 8;
if (Bpp == 3)
2004-11-28 07:56:58 +03:00
{
Bpp = 4;
2004-11-28 07:56:58 +03:00
}
2015-07-12 11:07:10 +03:00
make_stream(pixel_s);
make_stream(s);
2005-02-08 06:45:30 +03:00
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 3);
2005-02-08 06:45:30 +03:00
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)
2005-02-08 06:45:30 +03:00
{
break;
2005-02-08 06:45:30 +03:00
}
2005-02-08 06:45:30 +03:00
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 12);
2005-02-08 06:45:30 +03:00
if (error == 0)
2004-11-28 07:56:58 +03:00
{
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 (encoding == 0) /* raw */
2005-02-08 06:45:30 +03:00
{
need_size = cx * cy * Bpp;
2015-07-12 11:07:10 +03:00
init_stream(pixel_s, need_size);
error = trans_force_read_s(v->trans, pixel_s, need_size);
if (error == 0)
{
2015-07-12 11:07:10 +03:00
error = v->server_paint_rect(v, x, y, cx, cy, pixel_s->data, cx, cy, 0, 0);
}
}
else if (encoding == 1) /* copy rect */
{
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
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 == 0xffffff11) /* cursor */
{
g_memset(cursor_data, 0, 32 * (32 * 3));
g_memset(cursor_mask, 0, 32 * (32 / 8));
j = cx * cy * Bpp;
k = ((cx + 7) / 8) * cy;
init_stream(s, j + k);
2015-07-12 11:07:10 +03:00
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->mod_bpp);
split_color(pixel, &r, &g, &b, v->mod_bpp, v->palette);
pixel = make_color(r, g, b, 24);
set_pixel_safe(cursor_data, k, j, 32, 32, 24, pixel);
}
}
}
2016-01-14 19:32:09 +03:00
/* 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 == 0xffffff21) /* desktop size */
{
v->mod_width = cx;
v->mod_height = cy;
error = v->server_reset(v, cx, cy, v->mod_bpp);
}
else
{
g_sprintf(text, "VNC error in lib_framebuffer_update encoding = %8.8x",
encoding);
v->server_msg(v, text, 1);
2005-02-08 06:45:30 +03:00
}
2004-11-28 07:56:58 +03:00
}
}
2004-11-28 07:56:58 +03:00
if (error == 0)
{
error = v->server_end_update(v);
}
if (error == 0)
{
2019-04-10 04:24:24 +03:00
if (v->suppress_output == 0)
{
/* FramebufferUpdateRequest */
init_stream(s, 8192);
out_uint8(s, 3);
out_uint8(s, 1);
out_uint16_be(s, 0);
out_uint16_be(s, 0);
out_uint16_be(s, v->mod_width);
out_uint16_be(s, v->mod_height);
s_mark_end(s);
error = lib_send_copy(v, s);
}
}
free_stream(s);
2015-07-12 11:07:10 +03:00
free_stream(pixel_s);
return error;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2015-07-12 11:07:10 +03:00
/* clip data from the vnc server */
2017-03-12 19:35:00 +03:00
int
lib_clip_data(struct vnc *v)
2004-11-28 07:56:58 +03:00
{
struct stream *s;
struct stream *out_s;
int size;
int error;
2015-07-12 11:07:10 +03:00
free_stream(v->clip_data_s);
v->clip_data_s = 0;
make_stream(s);
2005-02-08 06:45:30 +03:00
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 7);
2004-11-28 07:56:58 +03:00
if (error == 0)
{
in_uint8s(s, 3);
in_uint32_be(s, size);
2015-07-12 11:07:10 +03:00
make_stream(v->clip_data_s);
init_stream(v->clip_data_s, size);
error = trans_force_read_s(v->trans, v->clip_data_s, size);
}
if (error == 0)
{
make_stream(out_s);
init_stream(out_s, 8192);
out_uint16_le(out_s, 2);
out_uint16_le(out_s, 0);
out_uint32_le(out_s, 0x90);
out_uint8(out_s, 0x0d);
out_uint8s(out_s, 35);
out_uint8(out_s, 0x10);
out_uint8s(out_s, 35);
out_uint8(out_s, 0x01);
out_uint8s(out_s, 35);
out_uint8(out_s, 0x07);
out_uint8s(out_s, 35);
out_uint8s(out_s, 4);
s_mark_end(out_s);
size = (int)(out_s->end - out_s->data);
error = v->server_send_to_channel(v, v->clip_chanid, out_s->data, size, size, 3);
free_stream(out_s);
}
2010-10-26 15:08:28 +04:00
free_stream(s);
return error;
2010-10-26 15:08:28 +04:00
}
2004-11-28 07:56:58 +03:00
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_palette_update(struct vnc *v)
2004-11-28 07:56:58 +03:00
{
struct stream *s;
int first_color;
int num_colors;
int i;
int r;
int g;
int b;
int error;
2004-11-28 07:56:58 +03:00
make_stream(s);
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 5);
if (error == 0)
2004-11-28 07:56:58 +03:00
{
in_uint8s(s, 1);
in_uint16_be(s, first_color);
in_uint16_be(s, num_colors);
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, num_colors * 6);
2004-11-28 07:56:58 +03:00
}
if (error == 0)
2004-11-28 07:56:58 +03:00
{
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);
2010-10-26 15:08:28 +04:00
}
if (error == 0)
2010-10-26 15:08:28 +04:00
{
error = v->server_palette(v, v->palette);
2004-11-28 07:56:58 +03:00
}
if (error == 0)
2004-11-28 07:56:58 +03:00
{
error = v->server_end_update(v);
2005-02-08 06:45:30 +03:00
}
free_stream(s);
return error;
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_bell_trigger(struct vnc *v)
{
int error;
error = v->server_bell_trigger(v);
return error;
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_mod_signal(struct vnc *v)
2015-07-12 11:07:10 +03:00
{
g_writeln("lib_mod_signal: not used");
return 0;
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
static int
2015-07-12 11:07:10 +03:00
lib_mod_process_message(struct vnc *v, struct stream *s)
{
char type;
int error;
char text[256];
2015-07-12 11:07:10 +03:00
in_uint8(s, type);
2015-07-12 11:07:10 +03:00
error = 0;
if (error == 0)
2005-02-08 06:45:30 +03:00
{
if (type == 0) /* framebuffer update */
{
error = lib_framebuffer_update(v);
}
else if (type == 1) /* palette */
{
error = lib_palette_update(v);
}
else if (type == 2) /* bell */
{
error = lib_bell_trigger(v);
}
else if (type == 3) /* clipboard */
{
log_message(LOG_LEVEL_DEBUG, "VNC got clip data");
error = lib_clip_data(v);
}
else
{
2019-04-10 04:24:24 +03:00
g_sprintf(text, "VNC unknown in lib_mod_process_message %d", type);
v->server_msg(v, text, 1);
}
2004-11-28 07:56:58 +03:00
}
return error;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_mod_start(struct vnc *v, int w, int h, int bpp)
2004-11-28 07:56:58 +03:00
{
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_width = w;
v->server_height = h;
v->server_bpp = bpp;
return 0;
2004-11-28 07:56:58 +03:00
}
2007-01-25 08:34:14 +03:00
/******************************************************************************/
2017-03-12 19:35:00 +03:00
static int
lib_open_clip_channel(struct vnc *v)
2007-01-25 08:34:14 +03:00
{
char init_data[12] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
v->clip_chanid = v->server_get_channel_id(v, "cliprdr");
if (v->clip_chanid >= 0)
{
v->server_send_to_channel(v, v->clip_chanid, init_data, 12, 12, 3);
}
return 0;
2007-01-25 08:34:14 +03:00
}
2015-07-12 11:07:10 +03:00
/******************************************************************************/
2017-03-12 19:35:00 +03:00
static int
2015-07-12 11:07:10 +03:00
lib_data_in(struct trans *trans)
{
struct vnc *self;
struct stream *s;
LLOGLN(10, ("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)
{
g_writeln("lib_data_in: lib_mod_process_message failed");
return 1;
}
init_stream(s, 0);
return 0;
}
2004-11-28 07:56:58 +03:00
/******************************************************************************/
2005-01-07 04:05:30 +03:00
/*
return error
*/
2017-03-12 19:35:00 +03:00
int
lib_mod_connect(struct vnc *v)
2004-11-28 07:56:58 +03:00
{
char cursor_data[32 * (32 * 3)];
char cursor_mask[32 * (32 / 8)];
char con_port[256];
char text[256];
struct stream *s;
struct stream *pixel_format;
int error;
int i;
int check_sec_result;
2015-07-12 11:07:10 +03:00
struct source_info *si;
v->server_msg(v, "VNC started connecting", 0);
check_sec_result = 1;
/* 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;
2005-02-04 06:41:54 +03:00
}
if (g_strcmp(v->ip, "") == 0)
2005-02-04 06:41:54 +03:00
{
v->server_msg(v, "VNC error - no ip set", 0);
return 1;
2005-01-07 04:05:30 +03:00
}
make_stream(s);
g_sprintf(con_port, "%s", v->port);
make_stream(pixel_format);
2015-07-12 11:07:10 +03:00
v->trans = trans_create(TRANS_MODE_TCP, 8 * 8192, 8192);
if (v->trans == 0)
{
2015-07-12 11:07:10 +03:00
v->server_msg(v, "VNC error: trans_create() failed", 0);
2014-07-28 02:29:12 +04:00
free_stream(s);
free_stream(pixel_format);
return 1;
}
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);
}
g_sprintf(text, "VNC connecting to %s %s", v->ip, con_port);
v->server_msg(v, text, 0);
2015-07-12 11:07:10 +03:00
si = (struct source_info *) (v->si);
v->trans->si = si;
v->trans->my_source = XRDP_SOURCE_MOD;
error = trans_connect(v->trans, v->ip, con_port, 3000);
2005-02-08 06:45:30 +03:00
if (error == 0)
2004-11-28 07:56:58 +03:00
{
v->server_msg(v, "VNC tcp connected", 0);
2016-01-14 19:32:09 +03:00
/* protocol version */
2005-02-08 06:45:30 +03:00
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 12);
2005-02-08 06:45:30 +03:00
if (error == 0)
{
2015-07-12 11:07:10 +03:00
s->p = s->data;
out_uint8a(s, "RFB 003.003\n", 12);
s_mark_end(s);
error = trans_force_write_s(v->trans, s);
}
/* sec type */
if (error == 0)
{
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 4);
}
if (error == 0)
{
in_uint32_be(s, i);
g_sprintf(text, "VNC security level is %d (1 = none, 2 = standard)", i);
v->server_msg(v, text, 0);
if (i == 1) /* none */
{
check_sec_result = 0;
}
else if (i == 2) /* dec the password and the server random */
{
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 16);
if (error == 0)
{
2015-07-12 11:07:10 +03:00
init_stream(s, 8192);
if (v->got_guid)
{
char guid_str[64];
2016-12-05 02:39:10 +03:00
g_bytes_to_hexstr(v->guid, 16, guid_str, 64);
rfbHashEncryptBytes(s->data, guid_str);
}
else
{
rfbEncryptBytes(s->data, v->password);
}
2015-07-12 11:07:10 +03:00
s->p += 16;
s_mark_end(s);
error = trans_force_write_s(v->trans, s);
check_sec_result = 1; // not needed
}
}
else if (i == 0)
{
log_message(LOG_LEVEL_DEBUG, "VNC Server will disconnect");
error = 1;
}
else
{
log_message(LOG_LEVEL_DEBUG, "VNC unsupported security level");
error = 1;
}
}
}
if (error != 0)
{
log_message(LOG_LEVEL_DEBUG, "VNC error %d after security negotiation",
error);
}
if (error == 0 && check_sec_result)
{
/* sec result */
init_stream(s, 8192);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 4);
if (error == 0)
{
in_uint32_be(s, i);
if (i != 0)
{
v->server_msg(v, "VNC password failed", 0);
error = 2;
}
else
{
v->server_msg(v, "VNC password ok", 0);
}
}
}
2005-02-08 06:45:30 +03:00
if (error == 0)
2005-02-04 06:41:54 +03:00
{
v->server_msg(v, "VNC sending share flag", 0);
init_stream(s, 8192);
s->data[0] = 1;
2015-07-12 11:07:10 +03:00
s->p++;
s_mark_end(s);
error = trans_force_write_s(v->trans, s); /* share flag */
2005-01-07 04:05:30 +03:00
}
2004-11-28 07:56:58 +03:00
else
{
log_message(LOG_LEVEL_DEBUG, "VNC error before sending share flag");
}
if (error == 0)
{
v->server_msg(v, "VNC receiving server init", 0);
2015-07-12 11:07:10 +03:00
init_stream(s, 8192);
error = trans_force_read_s(v->trans, s, 4); /* server init */
}
else
{
log_message(LOG_LEVEL_DEBUG, "VNC error before receiving server init");
}
if (error == 0)
{
in_uint16_be(s, v->mod_width);
in_uint16_be(s, v->mod_height);
init_stream(pixel_format, 8192);
v->server_msg(v, "VNC receiving pixel format", 0);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, pixel_format, 16);
}
else
{
log_message(LOG_LEVEL_DEBUG, "VNC error before receiving pixel format");
}
if (error == 0)
{
v->mod_bpp = v->server_bpp;
init_stream(s, 8192);
v->server_msg(v, "VNC receiving name length", 0);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, 4); /* name len */
}
else
{
log_message(LOG_LEVEL_DEBUG, "VNC error before receiving name length");
}
if (error == 0)
{
in_uint32_be(s, i);
if (i > 255 || i < 0)
{
error = 3;
}
else
{
2015-07-12 11:07:10 +03:00
init_stream(s, 8192);
v->server_msg(v, "VNC receiving name", 0);
2015-07-12 11:07:10 +03:00
error = trans_force_read_s(v->trans, s, i); /* name len */
g_memcpy(v->mod_name, s->data, i);
v->mod_name[i] = 0;
}
}
else
{
log_message(LOG_LEVEL_DEBUG, "VNC error before receiving name");
}
/* should be connected */
if (error == 0)
{
/* SetPixelFormat */
init_stream(s, 8192);
out_uint8(s, 0);
out_uint8(s, 0);
out_uint8(s, 0);
out_uint8(s, 0);
init_stream(pixel_format, 8192);
if (v->mod_bpp == 8)
{
out_uint8(pixel_format, 8); /* bits per pixel */
out_uint8(pixel_format, 8); /* depth */
2005-07-13 05:07:00 +04:00
#if defined(B_ENDIAN)
out_uint8(pixel_format, 1); /* big endian */
2005-07-10 23:17:09 +04:00
#else
out_uint8(pixel_format, 0); /* big endian */
2005-07-10 23:17:09 +04:00
#endif
out_uint8(pixel_format, 0); /* true color flag */
out_uint16_be(pixel_format, 0); /* red max */
out_uint16_be(pixel_format, 0); /* green max */
out_uint16_be(pixel_format, 0); /* blue max */
out_uint8(pixel_format, 0); /* red shift */
out_uint8(pixel_format, 0); /* green shift */
out_uint8(pixel_format, 0); /* blue shift */
out_uint8s(pixel_format, 3); /* pad */
}
else if (v->mod_bpp == 15)
{
out_uint8(pixel_format, 16); /* bits per pixel */
out_uint8(pixel_format, 15); /* depth */
2009-02-23 11:45:41 +03:00
#if defined(B_ENDIAN)
out_uint8(pixel_format, 1); /* big endian */
2009-02-23 11:45:41 +03:00
#else
out_uint8(pixel_format, 0); /* big endian */
2009-02-23 11:45:41 +03:00
#endif
out_uint8(pixel_format, 1); /* true color flag */
out_uint16_be(pixel_format, 31); /* red max */
out_uint16_be(pixel_format, 31); /* green max */
out_uint16_be(pixel_format, 31); /* blue max */
out_uint8(pixel_format, 10); /* red shift */
out_uint8(pixel_format, 5); /* green shift */
out_uint8(pixel_format, 0); /* blue shift */
out_uint8s(pixel_format, 3); /* pad */
}
else if (v->mod_bpp == 16)
{
out_uint8(pixel_format, 16); /* bits per pixel */
out_uint8(pixel_format, 16); /* depth */
2005-07-13 05:07:00 +04:00
#if defined(B_ENDIAN)
out_uint8(pixel_format, 1); /* big endian */
2005-07-10 23:17:09 +04:00
#else
out_uint8(pixel_format, 0); /* big endian */
2005-07-10 23:17:09 +04:00
#endif
out_uint8(pixel_format, 1); /* true color flag */
out_uint16_be(pixel_format, 31); /* red max */
out_uint16_be(pixel_format, 63); /* green max */
out_uint16_be(pixel_format, 31); /* blue max */
out_uint8(pixel_format, 11); /* red shift */
out_uint8(pixel_format, 5); /* green shift */
out_uint8(pixel_format, 0); /* blue shift */
out_uint8s(pixel_format, 3); /* pad */
}
else if (v->mod_bpp == 24 || v->mod_bpp == 32)
{
out_uint8(pixel_format, 32); /* bits per pixel */
out_uint8(pixel_format, 24); /* depth */
2007-04-29 11:33:11 +04:00
#if defined(B_ENDIAN)
out_uint8(pixel_format, 1); /* big endian */
2007-04-29 11:33:11 +04:00
#else
out_uint8(pixel_format, 0); /* big endian */
2007-04-29 11:33:11 +04:00
#endif
out_uint8(pixel_format, 1); /* true color flag */
out_uint16_be(pixel_format, 255); /* red max */
out_uint16_be(pixel_format, 255); /* green max */
out_uint16_be(pixel_format, 255); /* blue max */
out_uint8(pixel_format, 16); /* red shift */
out_uint8(pixel_format, 8); /* green shift */
out_uint8(pixel_format, 0); /* blue shift */
out_uint8s(pixel_format, 3); /* pad */
}
out_uint8a(s, pixel_format->data, 16);
v->server_msg(v, "VNC sending pixel format", 0);
2015-07-12 11:07:10 +03:00
s_mark_end(s);
error = trans_force_write_s(v->trans, s);
}
if (error == 0)
{
/* SetEncodings */
init_stream(s, 8192);
out_uint8(s, 2);
out_uint8(s, 0);
out_uint16_be(s, 4);
out_uint32_be(s, 0); /* raw */
out_uint32_be(s, 1); /* copy rect */
out_uint32_be(s, 0xffffff11); /* cursor */
out_uint32_be(s, 0xffffff21); /* desktop size */
v->server_msg(v, "VNC sending encodings", 0);
2015-07-12 11:07:10 +03:00
s_mark_end(s);
error = trans_force_write_s(v->trans, s);
}
if (error == 0)
{
error = v->server_reset(v, v->mod_width, v->mod_height, v->mod_bpp);
}
if (error == 0)
{
2019-04-10 04:24:24 +03:00
if (v->suppress_output == 0)
{
/* FramebufferUpdateRequest */
init_stream(s, 8192);
out_uint8(s, 3);
out_uint8(s, 0);
out_uint16_be(s, 0);
out_uint16_be(s, 0);
out_uint16_be(s, v->mod_width);
out_uint16_be(s, v->mod_height);
v->server_msg(v, "VNC sending framebuffer update request", 0);
s_mark_end(s);
error = trans_force_write_s(v->trans, s);
}
}
if (error == 0)
{
if (v->server_bpp != v->mod_bpp)
{
v->server_msg(v, "VNC error - server bpp and client bpp do not match", 0);
error = 1;
}
}
if (error == 0)
{
/* 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));
v->server_msg(v, "VNC sending cursor", 0);
error = v->server_set_cursor(v, 3, 3, cursor_data, cursor_mask);
}
free_stream(s);
free_stream(pixel_format);
if (error == 0)
{
v->server_msg(v, "VNC connection complete, connected ok", 0);
lib_open_clip_channel(v);
}
else
{
v->server_msg(v, "VNC error - problem connecting", 0);
}
2015-07-12 11:07:10 +03:00
if (error != 0)
{
trans_delete(v->trans);
v->trans = 0;
v->server_msg(v, "some problem", 0);
return 1;
}
else
{
v->server_msg(v, "connected ok", 0);
v->trans->trans_data_in = lib_data_in;
v->trans->header_size = 1;
v->trans->callback_data = v;
}
return error;
2004-11-28 07:56:58 +03:00
}
2005-01-07 04:05:30 +03:00
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_mod_end(struct vnc *v)
2005-01-07 04:05:30 +03:00
{
if (v->vnc_desktop != 0)
{
}
2015-07-12 11:07:10 +03:00
free_stream(v->clip_data_s);
return 0;
2005-01-07 04:05:30 +03:00
}
/******************************************************************************/
2017-03-12 19:35:00 +03:00
int
lib_mod_set_param(struct vnc *v, const char *name, const char *value)
2005-01-07 04:05:30 +03:00
{
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->got_guid = 1;
g_memcpy(v->guid, value, 16);
}
return 0;
2005-01-07 04:05:30 +03:00
}
2008-04-03 10:43:30 +04:00
/******************************************************************************/
/* return error */
2017-03-12 19:35:00 +03:00
int
lib_mod_get_wait_objs(struct vnc *v, tbus *read_objs, int *rcount,
tbus *write_objs, int *wcount, int *timeout)
2008-04-03 10:43:30 +04:00
{
2015-07-12 11:07:10 +03:00
LLOGLN(10, ("lib_mod_get_wait_objs:"));
2008-04-03 10:43:30 +04:00
if (v != 0)
2008-04-03 10:43:30 +04:00
{
2015-07-12 11:07:10 +03:00
if (v->trans != 0)
{
2015-07-12 11:07:10 +03:00
trans_get_wait_objs_rw(v->trans, read_objs, rcount,
2015-07-13 11:10:48 +03:00
write_objs, wcount, timeout);
}
2008-04-03 10:43:30 +04:00
}
return 0;
2008-04-03 10:43:30 +04:00
}
/******************************************************************************/
/* return error */
2017-03-12 19:35:00 +03:00
int
lib_mod_check_wait_objs(struct vnc *v)
2008-04-03 10:43:30 +04:00
{
int rv;
rv = 0;
if (v != 0)
2008-04-03 10:43:30 +04:00
{
2015-07-12 11:07:10 +03:00
if (v->trans != 0)
{
2015-07-12 11:07:10 +03:00
rv = trans_check_wait_objs(v->trans);
}
2008-04-03 10:43:30 +04:00
}
return rv;
2008-04-03 10:43:30 +04:00
}
2019-04-10 04:24:24 +03:00
/******************************************************************************/
/* return error */
int
lib_mod_frame_ack(struct vnc* v, int flags, int frame_id)
{
return 0;
}
/******************************************************************************/
/* return error */
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)
{
/* FramebufferUpdateRequest */
make_stream(s);
init_stream(s, 8192);
out_uint8(s, 3);
out_uint8(s, 0);
out_uint16_be(s, 0);
out_uint16_be(s, 0);
out_uint16_be(s, v->mod_width);
out_uint16_be(s, v->mod_height);
s_mark_end(s);
error = lib_send_copy(v, s);
free_stream(s);
}
return error;
}
2004-11-28 07:56:58 +03:00
/******************************************************************************/
tintptr EXPORT_CC
2005-07-13 05:07:00 +04:00
mod_init(void)
2004-11-28 07:56:58 +03:00
{
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;
2019-04-10 04:24:24 +03:00
v->mod_frame_ack = lib_mod_frame_ack;
v->mod_suppress_output = lib_mod_suppress_output;
return (tintptr) v;
2004-11-28 07:56:58 +03:00
}
/******************************************************************************/
2005-07-13 05:07:00 +04:00
int EXPORT_CC
mod_exit(tintptr handle)
2004-11-28 07:56:58 +03:00
{
struct vnc *v = (struct vnc *) handle;
log_message(LOG_LEVEL_DEBUG, "VNC mod_exit");
if (v == 0)
{
return 0;
}
2015-07-12 11:07:10 +03:00
trans_delete(v->trans);
g_free(v);
2004-11-28 07:56:58 +03:00
return 0;
}