/*
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2 as
* published by the Free Software Foundation.
*
* GTK VNC Widget
*/
#include "gvnc.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef __FreeBSD__
#include <sys/endian.h>
#define __BYTE_ORDER _BYTE_ORDER
#define __LITTLE_ENDIAN _LITTLE_ENDIAN
#define __BIG_ENDIAN _BIG_ENDIAN
#define bswap_16 bswap16
#define bswap_32 bswap32
#define bswap_64 bswap64
#else
#include <endian.h>
#endif
#include "coroutine.h"
#include "d3des.h"
#include "utils.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
struct wait_queue
{
gboolean waiting;
struct coroutine *context;
};
typedef void gvnc_blt_func(struct gvnc *, uint8_t *, int, int, int, int, int);
typedef void gvnc_hextile_func(struct gvnc *gvnc, uint8_t flags,
uint16_t x, uint16_t y,
uint16_t width, uint16_t height,
uint8_t *fg, uint8_t *bg);
/*
* A special GSource impl which allows us to wait on a certain
* condition to be satisified. This is effectively a boolean test
* run on each iteration of the main loop. So whenever a file has
* new I/O, or a timer occurrs, etc we'll do the check. This is
* pretty efficient compared to a normal GLib Idle func which has
* to busy wait on a timeout, since our condition is only checked
* when some other source's state changes
*/
typedef gboolean (*g_condition_wait_func)(gpointer);
struct g_condition_wait_source
{
GSource src;
struct coroutine *co;
g_condition_wait_func func;
gpointer data;
};
struct gvnc
{
GIOChannel *channel;
int fd;
char *host;
char *port;
struct gvnc_pixel_format fmt;
gboolean has_error;
int width;
int height;
char *name;
int major;
int minor;
gnutls_session_t tls_session;
/* Auth related params */
unsigned int auth_type;
unsigned int auth_subtype;
char *cred_username;
char *cred_password;
char *cred_x509_cacert;
char *cred_x509_cacrl;
char *cred_x509_cert;
char *cred_x509_key;
char read_buffer[4096];
size_t read_offset;
size_t read_size;
char write_buffer[4096];
size_t write_offset;
gboolean perfect_match;
struct gvnc_framebuffer local;
int rm, gm, bm;
int rrs, grs, brs;
int rls, gls, bls;
gvnc_blt_func *blt;
gvnc_hextile_func *hextile;
int shared_memory_enabled;
struct gvnc_ops ops;
gpointer ops_data;
int absolute;
int wait_interruptable;
struct wait_queue wait;
char *xmit_buffer;
int xmit_buffer_capacity;
int xmit_buffer_size;
};
#define nibhi(a) (((a) >> 4) & 0x0F)
#define niblo(a) ((a) & 0x0F)
/* Main loop helper functions */
static gboolean g_io_wait_helper(GIOChannel *channel G_GNUC_UNUSED,
GIOCondition cond,
gpointer data)
{
struct coroutine *to = data;
coroutine_yieldto(to, &cond);
return FALSE;
}
static GIOCondition g_io_wait(GIOChannel *channel, GIOCondition cond)
{
GIOCondition *ret;
g_io_add_watch(channel, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, g_io_wait_helper, coroutine_self());
ret = coroutine_yield(NULL);
return *ret;
}
static GIOCondition g_io_wait_interruptable(struct wait_queue *wait,
GIOChannel *channel,
GIOCondition cond)
{
GIOCondition *ret;
gint id;
wait->context = coroutine_self();
id = g_io_add_watch(channel, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, g_io_wait_helper, wait->context);
wait->waiting = TRUE;
ret = coroutine_yield(NULL);
wait->waiting = FALSE;
if (ret == NULL) {
g_source_remove(id);
return 0;
} else
return *ret;
}
static void g_io_wakeup(struct wait_queue *wait)
{
if (wait->waiting)
coroutine_yieldto(wait->context, NULL);
}
/*
* Call immediately before the main loop does an iteration. Returns
* true if the condition we're checking is ready for dispatch
*/
static gboolean g_condition_wait_prepare(GSource *src,
int *timeout) {
struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src;
*timeout = -1;
return vsrc->func(vsrc->data);
}
/*
* Call immediately after the main loop does an iteration. Returns
* true if the condition we're checking is ready for dispatch
*/
static gboolean g_condition_wait_check(GSource *src) {
struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src;
return vsrc->func(vsrc->data);
}
static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
GSourceFunc cb,
gpointer data) {
return cb(data);
}
GSourceFuncs waitFuncs = {
.prepare = g_condition_wait_prepare,
.check = g_condition_wait_check,
.dispatch = g_condition_wait_dispatch,
};
static gboolean g_condition_wait_helper(gpointer data)
{
struct coroutine *co = (struct coroutine *)data;
coroutine_yieldto(co, NULL);
return FALSE;
}
static gboolean g_condition_wait(g_condition_wait_func func, gpointer data)
{
GSource *src;
struct g_condition_wait_source *vsrc;
/* Short-circuit check in case we've got it ahead of time */
if (func(data)) {
return TRUE;
}
/*
* Don't have it, so yield to the main loop, checking the condition
* on each iteration of the main loop
*/
src = g_source_new(&waitFuncs, sizeof(struct g_condition_wait_source));
vsrc = (struct g_condition_wait_source *)src;
vsrc->func = func;
vsrc->data = data;
vsrc->co = coroutine_self();
g_source_attach(src, NULL);
g_source_set_callback(src, g_condition_wait_helper, coroutine_self(), NULL);
coroutine_yield(NULL);
return TRUE;
}
/* IO functions */
static int gvnc_read(struct gvnc *gvnc, void *data, size_t len)
{
int fd = g_io_channel_unix_get_fd(gvnc->channel);
char *ptr = data;
size_t offset = 0;
if (gvnc->has_error) return -EINVAL;
while (offset < len) {
size_t tmp;
if (gvnc->read_offset == gvnc->read_size) {
int ret;
if (gvnc->tls_session) {
ret = gnutls_read(gvnc->tls_session, gvnc->read_buffer, 4096);
if (ret < 0) {
if (ret == GNUTLS_E_AGAIN)
errno = EAGAIN;
else
errno = EIO;
ret = -1;
}
} else
ret = read(fd, gvnc->read_buffer, 4096);
if (ret == -1) {
switch (errno) {
case EAGAIN:
if (gvnc->wait_interruptable) {
if (!g_io_wait_interruptable(&gvnc->wait,
gvnc->channel, G_IO_IN))
return -EAGAIN;
} else
g_io_wait(gvnc->channel, G_IO_IN);
case EINTR:
continue;
default:
gvnc->has_error = TRUE;
return -errno;
}
}
if (ret == 0) {
gvnc->has_error = TRUE;
return -EPIPE;
}
gvnc->read_offset = 0;
gvnc->read_size = ret;
}
tmp = MIN(gvnc->read_size - gvnc->read_offset, len - offset);
memcpy(ptr + offset, gvnc->read_buffer + gvnc->read_offset, tmp);
gvnc->read_offset += tmp;
offset += tmp;
}
return 0;
}
static void gvnc_flush(struct gvnc *gvnc)
{
int fd = g_io_channel_unix_get_fd(gvnc->channel);
size_t offset = 0;
while (offset < gvnc->write_offset) {
int ret;
if (gvnc->tls_session) {
ret = gnutls_write(gvnc->tls_session,
gvnc->write_buffer+offset,
gvnc->write_offset-offset);
if (ret < 0) {
if (ret == GNUTLS_E_AGAIN)
errno = EAGAIN;
else
errno = EIO;
ret = -1;
}
} else
ret = write(fd,
gvnc->write_buffer+offset,
gvnc->write_offset-offset);
if (ret == -1) {
switch (errno) {
case EAGAIN:
g_io_wait(gvnc->channel, G_IO_OUT);
case EINTR:
continue;
default:
gvnc->has_error = TRUE;
return;
}
}
if (ret == 0) {
gvnc->has_error = TRUE;
return;
}
offset += ret;
}
gvnc->write_offset = 0;
}
static void gvnc_write(struct gvnc *gvnc, const void *data, size_t len)
{
const char *ptr = data;
size_t offset = 0;
while (offset < len) {
ssize_t tmp;
if (gvnc->write_offset == sizeof(gvnc->write_buffer)) {
gvnc_flush(gvnc);
}
tmp = MIN(sizeof(gvnc->write_buffer), len - offset);
memcpy(gvnc->write_buffer+gvnc->write_offset, ptr + offset, tmp);
gvnc->write_offset += tmp;
offset += tmp;
}
}
static ssize_t gvnc_tls_push(gnutls_transport_ptr_t transport,
const void *data,
size_t len) {
struct gvnc *gvnc = (struct gvnc *)transport;
int fd = g_io_channel_unix_get_fd(gvnc->channel);
int ret;
retry:
ret = write(fd, data, len);
if (ret < 0) {
if (errno == EINTR)
goto retry;
return -1;
}
return ret;
}
static ssize_t gvnc_tls_pull(gnutls_transport_ptr_t transport,
void *data,
size_t len) {
struct gvnc *gvnc = (struct gvnc *)transport;
int fd = g_io_channel_unix_get_fd(gvnc->channel);
int ret;
retry:
ret = read(fd, data, len);
if (ret < 0) {
if (errno == EINTR)
goto retry;
return -1;
}
return ret;
}
static uint8_t gvnc_read_u8(struct gvnc *gvnc)
{
uint8_t value = 0;
gvnc_read(gvnc, &value, sizeof(value));
return value;
}
static int gvnc_read_u8_interruptable(struct gvnc *gvnc, uint8_t *value)
{
int ret;
gvnc->wait_interruptable = 1;
ret = gvnc_read(gvnc, value, sizeof(*value));
gvnc->wait_interruptable = 0;
return ret;
}
static uint16_t gvnc_read_u16(struct gvnc *gvnc)
{
uint16_t value = 0;
gvnc_read(gvnc, &value, sizeof(value));
return ntohs(value);
}
static uint32_t gvnc_read_u32(struct gvnc *gvnc)
{
uint32_t value = 0;
gvnc_read(gvnc, &value, sizeof(value));
return ntohl(value);
}
static int32_t gvnc_read_s32(struct gvnc *gvnc)
{
int32_t value = 0;
gvnc_read(gvnc, &value, sizeof(value));
return ntohl(value);
}
static void gvnc_write_u8(struct gvnc *gvnc, uint8_t value)
{
gvnc_write(gvnc, &value, sizeof(value));
}
static void gvnc_write_u16(struct gvnc *gvnc, uint16_t value)
{
value = htons(value);
gvnc_write(gvnc, &value, sizeof(value));
}
static void gvnc_write_u32(struct gvnc *gvnc, uint32_t value)
{
value = htonl(value);
gvnc_write(gvnc, &value, sizeof(value));
}
static void gvnc_write_s32(struct gvnc *gvnc, int32_t value)
{
value = htonl(value);
gvnc_write(gvnc, &value, sizeof(value));
}
#define DH_BITS 1024
static gnutls_dh_params_t dh_params;
static gboolean gvnc_tls_initialize(void)
{
static int tlsinitialized = 0;
if (tlsinitialized)
return TRUE;
if (gnutls_global_init () < 0)
return FALSE;
if (gnutls_dh_params_init (&dh_params) < 0)
return FALSE;
if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0)
return FALSE;
tlsinitialized = TRUE;
return TRUE;
}
static gnutls_anon_client_credentials gvnc_tls_initialize_anon_cred(void)
{
gnutls_anon_client_credentials anon_cred;
int ret;
if ((ret = gnutls_anon_allocate_client_credentials(&anon_cred)) < 0) {
GVNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
return NULL;
}
return anon_cred;
}
static gnutls_certificate_credentials_t gvnc_tls_initialize_cert_cred(struct gvnc *vnc)
{
gnutls_certificate_credentials_t x509_cred;
int ret;
if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
GVNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
return NULL;
}
if (vnc->cred_x509_cacert) {
if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred,
vnc->cred_x509_cacert,
GNUTLS_X509_FMT_PEM)) < 0) {
GVNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret));
return NULL;
}
} else {
GVNC_DEBUG("No CA certificate provided\n");
return NULL;
}
if (vnc->cred_x509_cert && vnc->cred_x509_key) {
if ((ret = gnutls_certificate_set_x509_key_file (x509_cred,
vnc->cred_x509_cert,
vnc->cred_x509_key,
GNUTLS_X509_FMT_PEM)) < 0) {
GVNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret));
return NULL;
}
} else {
GVNC_DEBUG("No client cert or key provided\n");
}
if (vnc->cred_x509_cacrl) {
if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred,
vnc->cred_x509_cacrl,
GNUTLS_X509_FMT_PEM)) < 0) {
GVNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret));
return NULL;
}
} else {
GVNC_DEBUG("No CA revocation list provided\n");
}
gnutls_certificate_set_dh_params (x509_cred, dh_params);
return x509_cred;
}
static int gvnc_validate_certificate(struct gvnc *vnc)
{
int ret;
unsigned int status;
const gnutls_datum_t *certs;
unsigned int nCerts, i;
time_t now;
GVNC_DEBUG("Validating\n");
if ((ret = gnutls_certificate_verify_peers2 (vnc->tls_session, &status)) < 0) {
GVNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret));
return FALSE;
}
if ((now = time(NULL)) == ((time_t)-1)) {
return FALSE;
}
if (status != 0) {
if (status & GNUTLS_CERT_INVALID)
GVNC_DEBUG ("The certificate is not trusted.\n");
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
GVNC_DEBUG ("The certificate hasn't got a known issuer.\n");
if (status & GNUTLS_CERT_REVOKED)
GVNC_DEBUG ("The certificate has been revoked.\n");
if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
GVNC_DEBUG ("The certificate uses an insecure algorithm\n");
return FALSE;
} else {
GVNC_DEBUG("Certificate is valid!\n");
}
if (gnutls_certificate_type_get(vnc->tls_session) != GNUTLS_CRT_X509)
return FALSE;
if (!(certs = gnutls_certificate_get_peers(vnc->tls_session, &nCerts)))
return FALSE;
for (i = 0 ; i < nCerts ; i++) {
gnutls_x509_crt_t cert;
GVNC_DEBUG ("Checking chain %d\n", i);
if (gnutls_x509_crt_init (&cert) < 0)
return FALSE;
if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
gnutls_x509_crt_deinit (cert);
return FALSE;
}
if (gnutls_x509_crt_get_expiration_time (cert) < now) {
GVNC_DEBUG("The certificate has expired\n");
gnutls_x509_crt_deinit (cert);
return FALSE;
}
if (gnutls_x509_crt_get_activation_time (cert) > now) {
GVNC_DEBUG("The certificate is not yet activated\n");
gnutls_x509_crt_deinit (cert);
return FALSE;
}
if (gnutls_x509_crt_get_activation_time (cert) > now) {
GVNC_DEBUG("The certificate is not yet activated\n");
gnutls_x509_crt_deinit (cert);
return FALSE;
}
if (i == 0) {
if (!vnc->host) {
GVNC_DEBUG ("No hostname provided for certificate verification\n");
gnutls_x509_crt_deinit (cert);
return FALSE;
}
if (!gnutls_x509_crt_check_hostname (cert, vnc->host)) {
GVNC_DEBUG ("The certificate's owner does not match hostname '%s'\n",
vnc->host);
gnutls_x509_crt_deinit (cert);
return FALSE;
}
}
}
return TRUE;
}
static void gvnc_read_pixel_format(struct gvnc *gvnc, struct gvnc_pixel_format *fmt)
{
uint8_t pad[3];
fmt->bits_per_pixel = gvnc_read_u8(gvnc);
fmt->depth = gvnc_read_u8(gvnc);
fmt->byte_order = gvnc_read_u8(gvnc) ? __BIG_ENDIAN : __LITTLE_ENDIAN;
fmt->true_color_flag = gvnc_read_u8(gvnc);
fmt->red_max = gvnc_read_u16(gvnc);
fmt->green_max = gvnc_read_u16(gvnc);
fmt->blue_max = gvnc_read_u16(gvnc);
fmt->red_shift = gvnc_read_u8(gvnc);
fmt->green_shift = gvnc_read_u8(gvnc);
fmt->blue_shift = gvnc_read_u8(gvnc);
gvnc_read(gvnc, pad, 3);
GVNC_DEBUG("Pixel format BPP: %d, Depth: %d, Byte order: %d, True color: %d\n"
" Mask red: %3d, green: %3d, blue: %3d\n"
" Shift red: %3d, green: %3d, blue: %3d\n",
fmt->bits_per_pixel, fmt->depth, fmt->byte_order, fmt->true_color_flag,
fmt->red_max, fmt->green_max, fmt->blue_max,
fmt->red_shift, fmt->green_shift, fmt->blue_shift);
}
/* initialize function */
gboolean gvnc_has_error(struct gvnc *gvnc)
{
return gvnc->has_error;
}
gboolean gvnc_set_pixel_format(struct gvnc *gvnc,
const struct gvnc_pixel_format *fmt)
{
uint8_t pad[3] = {0};
gvnc_write_u8(gvnc, 0);
gvnc_write(gvnc, pad, 3);
gvnc_write_u8(gvnc, fmt->bits_per_pixel);
gvnc_write_u8(gvnc, fmt->depth);
gvnc_write_u8(gvnc, fmt->byte_order == __BIG_ENDIAN ? 1 : 0);
gvnc_write_u8(gvnc, fmt->true_color_flag);
gvnc_write_u16(gvnc, fmt->red_max);
gvnc_write_u16(gvnc, fmt->green_max);
gvnc_write_u16(gvnc, fmt->blue_max);
gvnc_write_u8(gvnc, fmt->red_shift);
gvnc_write_u8(gvnc, fmt->green_shift);
gvnc_write_u8(gvnc, fmt->blue_shift);
gvnc_write(gvnc, pad, 3);
gvnc_flush(gvnc);
memcpy(&gvnc->fmt, fmt, sizeof(*fmt));
return !gvnc_has_error(gvnc);
}
gboolean gvnc_set_shared_buffer(struct gvnc *gvnc, int line_size, int shmid)
{
gvnc_write_u8(gvnc, 255);
gvnc_write_u8(gvnc, 0);
gvnc_write_u16(gvnc, line_size);
gvnc_write_u32(gvnc, shmid);
gvnc_flush(gvnc);
return !gvnc_has_error(gvnc);
}
gboolean gvnc_set_encodings(struct gvnc *gvnc, int n_encoding, int32_t *encoding)
{
uint8_t pad[1] = {0};
int i;
gvnc_write_u8(gvnc, 2);
gvnc_write(gvnc, pad, 1);
gvnc_write_u16(gvnc, n_encoding);
for (i = 0; i < n_encoding; i++)
gvnc_write_s32(gvnc, encoding[i]);
gvnc_flush(gvnc);
return !gvnc_has_error(gvnc);
}
gboolean gvnc_framebuffer_update_request(struct gvnc *gvnc,
uint8_t incremental,
uint16_t x, uint16_t y,
uint16_t width, uint16_t height)
{
gvnc_write_u8(gvnc, 3);
gvnc_write_u8(gvnc, incremental);
gvnc_write_u16(gvnc, x);
gvnc_write_u16(gvnc, y);
gvnc_write_u16(gvnc, width);
gvnc_write_u16(gvnc, height);
gvnc_flush(gvnc);
return !gvnc_has_error(gvnc);
}
static void gvnc_buffered_write(struct gvnc *gvnc, const void *data, size_t size)
{
size_t left;
left = gvnc->xmit_buffer_capacity - gvnc->xmit_buffer_size;
if (left < size) {
gvnc->xmit_buffer_capacity += size + 4095;
gvnc->xmit_buffer_capacity &= ~4095;
gvnc->xmit_buffer = g_realloc(gvnc->xmit_buffer, gvnc->xmit_buffer_capacity);
}
memcpy(&gvnc->xmit_buffer[gvnc->xmit_buffer_size],
data, size);
gvnc->xmit_buffer_size += size;
}
static void gvnc_buffered_write_u8(struct gvnc *gvnc, uint8_t value)
{
gvnc_buffered_write(gvnc, &value, 1);
}
static void gvnc_buffered_write_u16(struct gvnc *gvnc, uint16_t value)
{
value = htons(value);
gvnc_buffered_write(gvnc, &value, 2);
}
static void gvnc_buffered_write_u32(struct gvnc *gvnc, uint32_t value)
{
value = htonl(value);
gvnc_buffered_write(gvnc, &value, 4);
}
static void gvnc_buffered_flush(struct gvnc *gvnc)
{
g_io_wakeup(&gvnc->wait);
}
gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag, uint32_t key)
{
uint8_t pad[2] = {0};
gvnc_buffered_write_u8(gvnc, 4);
gvnc_buffered_write_u8(gvnc, down_flag);
gvnc_buffered_write(gvnc, pad, 2);
gvnc_buffered_write_u32(gvnc, key);
gvnc_buffered_flush(gvnc);
return !gvnc_has_error(gvnc);
}
gboolean gvnc_pointer_event(struct gvnc *gvnc, uint8_t button_mask,
uint16_t x, uint16_t y)
{
gvnc_buffered_write_u8(gvnc, 5);
gvnc_buffered_write_u8(gvnc, button_mask);
gvnc_buffered_write_u16(gvnc, x);
gvnc_buffered_write_u16(gvnc, y);
gvnc_buffered_flush(gvnc);
return !gvnc_has_error(gvnc);
}
gboolean gvnc_client_cut_text(struct gvnc *gvnc,
const void *data, size_t length)
{
uint8_t pad[3] = {0};
gvnc_write_u8(gvnc, 6);
gvnc_write(gvnc, pad, 3);
gvnc_write_u32(gvnc, length);
gvnc_write(gvnc, data, length);
gvnc_flush(gvnc);
return !gvnc_has_error(gvnc);
}
static inline uint8_t *gvnc_get_local(struct gvnc *gvnc, int x, int y)
{
return gvnc->local.data +
(y * gvnc->local.linesize) +
(x * gvnc->local.bpp);
}
#define SPLICE_I(a, b) a ## b
#define SPLICE(a, b) SPLICE_I(a, b)
#define SRC 8
#include "blt1.h"
#undef SRC
#define SRC 16
#include "blt1.h"
#undef SRC
#define SRC 32
#include "blt1.h"
#undef SRC
static gvnc_blt_func *gvnc_blt_table[3][3] = {
{ gvnc_blt_8x8, gvnc_blt_8x16, gvnc_blt_8x32 },
{ gvnc_blt_16x8, gvnc_blt_16x16, gvnc_blt_16x32 },
{ gvnc_blt_32x8, gvnc_blt_32x16, gvnc_blt_32x32 },
};
static gvnc_hextile_func *gvnc_hextile_table[3][3] = {
{ (gvnc_hextile_func *)gvnc_hextile_8x8,
(gvnc_hextile_func *)gvnc_hextile_8x16,
(gvnc_hextile_func *)gvnc_hextile_8x32 },
{ (gvnc_hextile_func *)gvnc_hextile_16x8,
(gvnc_hextile_func *)gvnc_hextile_16x16,
(gvnc_hextile_func *)gvnc_hextile_16x32 },
{ (gvnc_hextile_func *)gvnc_hextile_32x8,
(gvnc_hextile_func *)gvnc_hextile_32x16,
(gvnc_hextile_func *)gvnc_hextile_32x32 },
};
/* a fast blit for the perfect match scenario */
static void gvnc_blt_fast(struct gvnc *gvnc, uint8_t *src, int pitch,
int x, int y, int width, int height)
{
uint8_t *dst = gvnc_get_local(gvnc, x, y);
int i;
for (i = 0; i < height; i++) {
memcpy(dst, src, width * gvnc->local.bpp);
dst += gvnc->local.linesize;
src += pitch;
}
}
static void gvnc_blt(struct gvnc *gvnc, uint8_t *src, int pitch,
int x, int y, int width, int height)
{
gvnc->blt(gvnc, src, pitch, x, y, width, height);
}
static void gvnc_raw_update(struct gvnc *gvnc,
uint16_t x, uint16_t y,
uint16_t width, uint16_t height)
{
uint8_t *dst;
int i;
/* optimize for perfect match between server/client
FWIW, in the local case, we ought to be doing a write
directly from the source framebuffer and a read directly
into the client framebuffer
*/
if (gvnc->perfect_match) {
dst = gvnc_get_local(gvnc, x, y);
for (i = 0; i < height; i++) {
gvnc_read(gvnc, dst, width * gvnc->local.bpp);
dst += gvnc->local.linesize;
}
return;
}
dst = malloc(width * (gvnc->fmt.bits_per_pixel / 8));
if (dst == NULL) {
gvnc->has_error = TRUE;
return;
}
for (i = 0; i < height; i++) {
gvnc_read(gvnc, dst, width * (gvnc->fmt.bits_per_pixel / 8));
gvnc_blt(gvnc, dst, 0, x, y + i, width, 1);
}
free(dst);
}
static void gvnc_copyrect_update(struct gvnc *gvnc,
uint16_t dst_x, uint16_t dst_y,
uint16_t width, uint16_t height)
{
int src_x, src_y;
uint8_t *dst, *src;
int pitch = gvnc->local.linesize;
int i;
src_x = gvnc_read_u16(gvnc);
src_y = gvnc_read_u16(gvnc);
if (src_y < dst_y) {
pitch = -pitch;
src_y += (height - 1);
dst_y += (height - 1);
}
dst = gvnc_get_local(gvnc, dst_x, dst_y);
src = gvnc_get_local(gvnc, src_x, src_y);
for (i = 0; i < height; i++) {
memmove(dst, src, width * gvnc->local.bpp);
dst += pitch;
src += pitch;
}
}
static void gvnc_hextile_update(struct gvnc *gvnc,
uint16_t x, uint16_t y,
uint16_t width, uint16_t height)
{
uint8_t fg[4];
uint8_t bg[4];
int j;
for (j = 0; j < height; j += 16) {
int i;
for (i = 0; i < width; i += 16) {
uint8_t flags;
int w = MIN(16, width - i);
int h = MIN(16, height - j);
flags = gvnc_read_u8(gvnc);
gvnc->hextile(gvnc, flags, x + i, y + j, w, h, fg, bg);
}
}
}
static void gvnc_update(struct gvnc *gvnc, int x, int y, int width, int height)
{
if (gvnc->has_error || !gvnc->ops.update)
return;
if (!gvnc->ops.update(gvnc->ops_data, x, y, width, height))
gvnc->has_error = TRUE;
}
static void gvnc_set_color_map_entry(struct gvnc *gvnc, uint16_t color,
uint16_t red, uint16_t green,
uint16_t blue)
{
if (gvnc->has_error || !gvnc->ops.set_color_map_entry)
return;
if (!gvnc->ops.set_color_map_entry(gvnc->ops_data, color,
red, green, blue))
gvnc->has_error = TRUE;
}
static void gvnc_bell(struct gvnc *gvnc)
{
if (gvnc->has_error || !gvnc->ops.bell)
return;
GVNC_DEBUG("Server beep\n");
if (!gvnc->ops.bell(gvnc->ops_data))
gvnc->has_error = TRUE;
}
static void gvnc_server_cut_text(struct gvnc *gvnc, const void *data,
size_t len)
{
if (gvnc->has_error || !gvnc->ops.server_cut_text)
return;
GVNC_DEBUG("Server cut text\n");
if (!gvnc->ops.server_cut_text(gvnc->ops_data, data, len))
gvnc->has_error = TRUE;
}
static void gvnc_resize(struct gvnc *gvnc, int width, int height)
{
if (gvnc->has_error || !gvnc->ops.resize)
return;
if (!gvnc->ops.resize(gvnc->ops_data, width, height))
gvnc->has_error = TRUE;
}
static void gvnc_pointer_type_change(struct gvnc *gvnc, int absolute)
{
if (gvnc->has_error || !gvnc->ops.pointer_type_change)
return;
if (!gvnc->ops.pointer_type_change(gvnc->ops_data, absolute))
gvnc->has_error = TRUE;
}
static void gvnc_shared_memory_rmid(struct gvnc *gvnc, int shmid)
{
if (gvnc->has_error || !gvnc->ops.shared_memory_rmid)
return;
if (!gvnc->ops.shared_memory_rmid(gvnc->ops_data, shmid))
gvnc->has_error = TRUE;
}
#define RICH_CURSOR_BLIT(gvnc, pixbuf, image, mask, pitch, width, height, src_pixel_t) \
do { \
int x1, y1; \
uint8_t *src = image; \
uint32_t *dst = (uint32_t*)pixbuf; \
uint8_t *alpha = mask; \
for (y1 = 0; y1 < height; y1++) { \
src_pixel_t *sp = (src_pixel_t *)src; \
uint8_t *mp = alpha; \
for (x1 = 0; x1 < width; x1++) { \
*dst++ = (((mp[x1 / 8] >> (7 - (x1 % 8))) & 1) ? (255 << 24) : 0) \
| (((*sp >> gvnc->fmt.red_shift) & (gvnc->fmt.red_max)) << 16) \
| (((*sp >> gvnc->fmt.green_shift) & (gvnc->fmt.green_max)) << 8) \
| (((*sp >> gvnc->fmt.blue_shift) & (gvnc->fmt.blue_max)) << 0); \
sp++; \
} \
src += pitch; \
alpha += ((width + 7) / 8); \
} \
} while(0)
static void gvnc_rich_cursor(struct gvnc *gvnc, int x, int y, int width, int height)
{
uint8_t *pixbuf = NULL;
if (width && height) {
uint8_t *image, *mask;
int imagelen, masklen;
imagelen = width * height * (gvnc->fmt.bits_per_pixel / 8);
masklen = ((width + 7)/8) * height;
image = malloc(imagelen);
if (!image) {
gvnc->has_error = TRUE;
return;
}
mask = malloc(masklen);
if (!mask) {
free(image);
gvnc->has_error = TRUE;
return;
}
pixbuf = malloc(width * height * 4); /* RGB-A 8bit */
if (!pixbuf) {
free(mask);
free(image);
gvnc->has_error = TRUE;
return;
}
gvnc_read(gvnc, image, imagelen);
gvnc_read(gvnc, mask, masklen);
if (gvnc->fmt.bits_per_pixel == 8) {
RICH_CURSOR_BLIT(gvnc, pixbuf, image, mask, width * (gvnc->fmt.bits_per_pixel/8), width, height, uint8_t);
} else if (gvnc->fmt.bits_per_pixel == 16) {
RICH_CURSOR_BLIT(gvnc, pixbuf, image, mask, width * (gvnc->fmt.bits_per_pixel/8), width, height, uint16_t);
} else if (gvnc->fmt.bits_per_pixel == 24 || gvnc->fmt.bits_per_pixel == 32) {
RICH_CURSOR_BLIT(gvnc, pixbuf, image, mask, width * (gvnc->fmt.bits_per_pixel/8), width, height, uint32_t);
}
free(image);
free(mask);
}
if (gvnc->has_error || !gvnc->ops.local_cursor)
return;
if (!gvnc->ops.local_cursor(gvnc->ops_data, x, y, width, height, pixbuf))
gvnc->has_error = TRUE;
free(pixbuf);
}
static void gvnc_xcursor(struct gvnc *gvnc, int x, int y, int width, int height)
{
uint8_t *pixbuf = NULL;
if (width && height) {
uint8_t *data, *mask, *datap, *maskp;
uint32_t *pixp;
int rowlen;
int x1, y1;
uint8_t fgrgb[3], bgrgb[3];
uint32_t fg, bg;
gvnc_read(gvnc, fgrgb, 3);
gvnc_read(gvnc, bgrgb, 3);
fg = (255 << 24) | (fgrgb[0] << 16) | (fgrgb[1] << 8) | fgrgb[2];
bg = (255 << 24) | (bgrgb[0] << 16) | (bgrgb[1] << 8) | bgrgb[2];
rowlen = ((width + 7)/8);
if (!(data = malloc(rowlen*height))) {
gvnc->has_error = TRUE;
return;
}
if (!(mask = malloc(rowlen*height))) {
free(data);
gvnc->has_error = TRUE;
return;
}
pixbuf = malloc(width * height * 4); /* RGB-A 8bit */
gvnc_read(gvnc, data, rowlen*height);
gvnc_read(gvnc, mask, rowlen*height);
datap = data;
maskp = mask;
pixp = (uint32_t*)pixbuf;
for (y1 = 0; y1 < height; y1++) {
for (x1 = 0; x1 < width; x1++) {
*pixp++ = ((maskp[x1 / 8] >> (7-(x1 % 8))) & 1) ?
(((datap[x1 / 8] >> (7-(x1 % 8))) & 1) ? fg : bg) : 0;
}
datap += rowlen;
maskp += rowlen;
}
free(data);
free(mask);
}
if (gvnc->has_error || !gvnc->ops.local_cursor)
return;
if (!gvnc->ops.local_cursor(gvnc->ops_data, x, y, width, height, pixbuf))
gvnc->has_error = TRUE;
free(pixbuf);
}
static void gvnc_framebuffer_update(struct gvnc *gvnc, int32_t etype,
uint16_t x, uint16_t y,
uint16_t width, uint16_t height)
{
GVNC_DEBUG("FramebufferUpdate(%d, %d, %d, %d, %d)\n",
etype, x, y, width, height);
switch (etype) {
case GVNC_ENCODING_RAW:
gvnc_raw_update(gvnc, x, y, width, height);
break;
case GVNC_ENCODING_COPY_RECT:
gvnc_copyrect_update(gvnc, x, y, width, height);
break;
case GVNC_ENCODING_HEXTILE:
gvnc_hextile_update(gvnc, x, y, width, height);
break;
case GVNC_ENCODING_DESKTOP_RESIZE:
gvnc_resize(gvnc, width, height);
break;
case GVNC_ENCODING_POINTER_CHANGE:
gvnc_pointer_type_change(gvnc, x);
break;
case GVNC_ENCODING_SHARED_MEMORY:
switch (gvnc_read_u32(gvnc)) {
case 0:
gvnc->shared_memory_enabled = 1;
break;
case 1:
gvnc_shared_memory_rmid(gvnc, gvnc_read_u32(gvnc));
break;
case 2:
gvnc_resize(gvnc, gvnc->width, gvnc->height);
break;
case 3:
break;
}
break;
case GVNC_ENCODING_RICH_CURSOR:
gvnc_rich_cursor(gvnc, x, y, width, height);
break;
case GVNC_ENCODING_XCURSOR:
gvnc_xcursor(gvnc, x, y, width, height);
break;
default:
gvnc->has_error = TRUE;
break;
}
gvnc_update(gvnc, x, y, width, height);
}
gboolean gvnc_server_message(struct gvnc *gvnc)
{
uint8_t msg;
int ret;
/* NB: make sure that all server message functions
handle has_error appropriately */
do {
if (gvnc->xmit_buffer_size) {
gvnc_write(gvnc, gvnc->xmit_buffer, gvnc->xmit_buffer_size);
gvnc_flush(gvnc);
gvnc->xmit_buffer_size = 0;
}
} while ((ret = gvnc_read_u8_interruptable(gvnc, &msg)) == -EAGAIN);
if (ret < 0) {
GVNC_DEBUG("Aborting message processing on error\n");
return !gvnc_has_error(gvnc);
}
switch (msg) {
case 0: { /* FramebufferUpdate */
uint8_t pad[1];
uint16_t n_rects;
int i;
gvnc_read(gvnc, pad, 1);
n_rects = gvnc_read_u16(gvnc);
for (i = 0; i < n_rects; i++) {
uint16_t x, y, w, h;
int32_t etype;
x = gvnc_read_u16(gvnc);
y = gvnc_read_u16(gvnc);
w = gvnc_read_u16(gvnc);
h = gvnc_read_u16(gvnc);
etype = gvnc_read_s32(gvnc);
gvnc_framebuffer_update(gvnc, etype, x, y, w, h);
}
} break;
case 1: { /* SetColorMapEntries */
uint16_t first_color;
uint16_t n_colors;
uint8_t pad[1];
int i;
gvnc_read(gvnc, pad, 1);
first_color = gvnc_read_u16(gvnc);
n_colors = gvnc_read_u16(gvnc);
for (i = 0; i < n_colors; i++) {
uint16_t red, green, blue;
red = gvnc_read_u16(gvnc);
green = gvnc_read_u16(gvnc);
blue = gvnc_read_u16(gvnc);
gvnc_set_color_map_entry(gvnc,
i + first_color,
red, green, blue);
}
} break;
case 2: /* Bell */
gvnc_bell(gvnc);
break;
case 3: { /* ServerCutText */
uint8_t pad[3];
uint32_t n_text;
char *data;
gvnc_read(gvnc, pad, 3);
n_text = gvnc_read_u32(gvnc);
if (n_text > (32 << 20)) {
gvnc->has_error = TRUE;
break;
}
data = malloc(n_text + 1);
if (data == NULL) {
gvnc->has_error = TRUE;
break;
}
gvnc_read(gvnc, data, n_text);
data[n_text] = 0;
gvnc_server_cut_text(gvnc, data, n_text);
free(data);
} break;
default:
gvnc->has_error = TRUE;
break;
}
return !gvnc_has_error(gvnc);
}
static gboolean gvnc_check_auth_result(struct gvnc *gvnc)
{
uint32_t result;
GVNC_DEBUG("Checking auth result\n");
result = gvnc_read_u32(gvnc);
if (!result) {
GVNC_DEBUG("Success\n");
return TRUE;
}
if (gvnc->minor >= 8) {
uint32_t len;
char reason[1024];
len = gvnc_read_u32(gvnc);
if (len > (sizeof(reason)-1))
return FALSE;
gvnc_read(gvnc, reason, len);
reason[len] = '\0';
GVNC_DEBUG("Fail %s\n", reason);
if (!gvnc->has_error && gvnc->ops.auth_failure)
gvnc->ops.auth_failure(gvnc->ops_data, reason);
} else {
GVNC_DEBUG("Fail\n");
if (!gvnc->has_error && gvnc->ops.auth_failure)
gvnc->ops.auth_failure(gvnc->ops_data, NULL);
}
return FALSE;
}
static gboolean gvnc_perform_auth_vnc(struct gvnc *gvnc)
{
uint8_t challenge[16];
uint8_t key[8];
GVNC_DEBUG("Do Challenge\n");
if (!gvnc->cred_password)
return FALSE;
gvnc_read(gvnc, challenge, 16);
memset(key, 0, 8);
strncpy((char*)key, (char*)gvnc->cred_password, 8);
deskey(key, EN0);
des(challenge, challenge);
des(challenge + 8, challenge + 8);
gvnc_write(gvnc, challenge, 16);
gvnc_flush(gvnc);
return gvnc_check_auth_result(gvnc);
}
static gboolean gvnc_start_tls(struct gvnc *gvnc, int anonTLS) {
static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 };
static const int kx_priority[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0};
static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0};
int ret;
GVNC_DEBUG("Do TLS handshake\n");
if (gvnc_tls_initialize() < 0) {
GVNC_DEBUG("Failed to init TLS\n");
gvnc->has_error = TRUE;
return FALSE;
}
if (gvnc->tls_session == NULL) {
if (gnutls_init(&gvnc->tls_session, GNUTLS_CLIENT) < 0) {
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_set_default_priority(gvnc->tls_session) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_kx_set_priority(gvnc->tls_session, anonTLS ? kx_anon : kx_priority) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_certificate_type_set_priority(gvnc->tls_session, cert_type_priority) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_protocol_set_priority(gvnc->tls_session, protocol_priority) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (anonTLS) {
gnutls_anon_client_credentials anon_cred = gvnc_tls_initialize_anon_cred();
if (!anon_cred) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_credentials_set(gvnc->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
} else {
gnutls_certificate_credentials_t x509_cred = gvnc_tls_initialize_cert_cred(gvnc);
if (!x509_cred) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
if (gnutls_credentials_set(gvnc->tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
gnutls_deinit(gvnc->tls_session);
gvnc->has_error = TRUE;
return FALSE;
}
}
gnutls_transport_set_ptr(gvnc->tls_session, (gnutls_transport_ptr_t)gvnc);
gnutls_transport_set_push_function(gvnc->tls_session, gvnc_tls_push);
gnutls_transport_set_pull_function(gvnc->tls_session, gvnc_tls_pull);
}
retry:
if ((ret = gnutls_handshake(gvnc->tls_session)) < 0) {
if (!gnutls_error_is_fatal(ret)) {
GVNC_DEBUG("Handshake was blocking\n");
if (!gnutls_record_get_direction(gvnc->tls_session))
g_io_wait(gvnc->channel, G_IO_IN);
else
g_io_wait(gvnc->channel, G_IO_OUT);
goto retry;
}
GVNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
gnutls_deinit(gvnc->tls_session);
gvnc->tls_session = NULL;
gvnc->has_error = TRUE;
return FALSE;
}
GVNC_DEBUG("Handshake done\n");
if (anonTLS) {
return TRUE;
} else {
if (!gvnc_validate_certificate(gvnc)) {
GVNC_DEBUG("Certificate validation failed\n");
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
}
gboolean gvnc_wants_credential_password(struct gvnc *gvnc)
{
if (gvnc->auth_type == GVNC_AUTH_VNC)
return TRUE;
if (gvnc->auth_type == GVNC_AUTH_TLS &&
gvnc->auth_subtype == GVNC_AUTH_VNC)
return TRUE;
if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSVNC ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN)
return TRUE;
}
return FALSE;
}
gboolean gvnc_wants_credential_username(struct gvnc *gvnc)
{
if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN)
return TRUE;
}
return FALSE;
}
gboolean gvnc_wants_credential_x509(struct gvnc *gvnc)
{
if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509NONE ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN ||
gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC)
return TRUE;
}
return FALSE;
}
static gboolean gvnc_has_credentials(gpointer data)
{
struct gvnc *gvnc = (struct gvnc *)data;
if (gvnc->has_error)
return TRUE;
if (gvnc_wants_credential_username(gvnc) && !gvnc->cred_username)
return FALSE;
if (gvnc_wants_credential_password(gvnc) && !gvnc->cred_password)
return FALSE;
/*
* For x509 we require a minimum of the CA cert.
* Anything else is a bonus - though the server
* may reject auth if it decides it wants a client
* cert. We can't express that based on auth type
* alone though - we'll merely find out when TLS
* negotiation takes place.
*/
if (gvnc_wants_credential_x509(gvnc) && !gvnc->cred_x509_cacert)
return FALSE;
return TRUE;
}
static gboolean gvnc_gather_credentials(struct gvnc *gvnc)
{
if (!gvnc_has_credentials(gvnc)) {
GVNC_DEBUG("Requesting missing credentials\n");
if (gvnc->has_error || !gvnc->ops.auth_cred) {
gvnc->has_error = TRUE;
return TRUE;
}
if (!gvnc->ops.auth_cred(gvnc->ops_data))
gvnc->has_error = TRUE;
if (gvnc->has_error)
return TRUE;
GVNC_DEBUG("Waiting for missing credentials\n");
g_condition_wait(gvnc_has_credentials, gvnc);
GVNC_DEBUG("Got all credentials\n");
}
return !gvnc_has_error(gvnc);
}
static gboolean gvnc_has_auth_subtype(gpointer data)
{
struct gvnc *gvnc = (struct gvnc *)data;
if (gvnc->has_error)
return TRUE;
if (gvnc->auth_subtype == GVNC_AUTH_INVALID)
return FALSE;
return TRUE;
}
static gboolean gvnc_perform_auth_tls(struct gvnc *gvnc)
{
unsigned int nauth, i;
unsigned int auth[20];
if (!gvnc_start_tls(gvnc, 1)) {
GVNC_DEBUG("Could not start TLS\n");
return FALSE;
}
GVNC_DEBUG("Completed TLS setup\n");
nauth = gvnc_read_u8(gvnc);
if (gvnc_has_error(gvnc))
return FALSE;
if (nauth == 0)
return gvnc_check_auth_result(gvnc);
if (nauth > sizeof(auth)) {
GVNC_DEBUG("Too many (%d) auth types\n", nauth);
gvnc->has_error = TRUE;
return FALSE;
}
for (i = 0 ; i < nauth ; i++) {
auth[i] = gvnc_read_u8(gvnc);
}
for (i = 0 ; i < nauth ; i++) {
GVNC_DEBUG("Possible sub-auth %d\n", auth[i]);
}
if (gvnc->has_error || !gvnc->ops.auth_subtype)
return FALSE;
if (!gvnc->ops.auth_subtype(gvnc->ops_data, nauth, auth))
gvnc->has_error = TRUE;
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Waiting for auth subtype\n");
g_condition_wait(gvnc_has_auth_subtype, gvnc);
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Choose auth %d\n", gvnc->auth_subtype);
if (!gvnc_gather_credentials(gvnc))
return FALSE;
gvnc_write_u8(gvnc, gvnc->auth_subtype);
gvnc_flush(gvnc);
switch (gvnc->auth_subtype) {
case GVNC_AUTH_NONE:
if (gvnc->minor == 8)
return gvnc_check_auth_result(gvnc);
return TRUE;
case GVNC_AUTH_VNC:
return gvnc_perform_auth_vnc(gvnc);
default:
return FALSE;
}
return TRUE;
}
static gboolean gvnc_perform_auth_vencrypt(struct gvnc *gvnc)
{
int major, minor, status, anonTLS;
unsigned int nauth, i;
unsigned int auth[20];
major = gvnc_read_u8(gvnc);
minor = gvnc_read_u8(gvnc);
if (major != 0 &&
minor != 2) {
GVNC_DEBUG("Unsupported VeNCrypt version %d %d\n", major, minor);
return FALSE;
}
gvnc_write_u8(gvnc, major);
gvnc_write_u8(gvnc, minor);
gvnc_flush(gvnc);
status = gvnc_read_u8(gvnc);
if (status != 0) {
GVNC_DEBUG("Server refused VeNCrypt version %d %d\n", major, minor);
return FALSE;
}
nauth = gvnc_read_u8(gvnc);
if (nauth > (sizeof(auth)/sizeof(auth[0]))) {
GVNC_DEBUG("Too many (%d) auth types\n", nauth);
return FALSE;
}
for (i = 0 ; i < nauth ; i++) {
auth[i] = gvnc_read_u32(gvnc);
}
for (i = 0 ; i < nauth ; i++) {
GVNC_DEBUG("Possible auth %d\n", auth[i]);
}
if (gvnc->has_error || !gvnc->ops.auth_subtype)
return FALSE;
if (!gvnc->ops.auth_subtype(gvnc->ops_data, nauth, auth))
gvnc->has_error = TRUE;
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Waiting for auth subtype\n");
g_condition_wait(gvnc_has_auth_subtype, gvnc);
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Choose auth %d\n", gvnc->auth_subtype);
if (!gvnc_gather_credentials(gvnc))
return FALSE;
#if !DEBUG
if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN) {
GVNC_DEBUG("Cowardly refusing to transmit plain text password\n");
return FALSE;
}
#endif
gvnc_write_u32(gvnc, gvnc->auth_subtype);
gvnc_flush(gvnc);
status = gvnc_read_u8(gvnc);
if (status != 1) {
GVNC_DEBUG("Server refused VeNCrypt auth %d %d\n", gvnc->auth_subtype, status);
return FALSE;
}
switch (gvnc->auth_subtype) {
case GVNC_AUTH_VENCRYPT_TLSNONE:
case GVNC_AUTH_VENCRYPT_TLSPLAIN:
case GVNC_AUTH_VENCRYPT_TLSVNC:
anonTLS = 1;
break;
default:
anonTLS = 0;
}
if (!gvnc_start_tls(gvnc, anonTLS)) {
GVNC_DEBUG("Could not start TLS\n");
return FALSE;
}
GVNC_DEBUG("Completed TLS setup\n");
switch (gvnc->auth_subtype) {
/* Plain certificate based auth */
case GVNC_AUTH_VENCRYPT_TLSNONE:
case GVNC_AUTH_VENCRYPT_X509NONE:
GVNC_DEBUG("Completing auth\n");
return gvnc_check_auth_result(gvnc);
/* Regular VNC layered over TLS */
case GVNC_AUTH_VENCRYPT_TLSVNC:
case GVNC_AUTH_VENCRYPT_X509VNC:
GVNC_DEBUG("Handing off to VNC auth\n");
return gvnc_perform_auth_vnc(gvnc);
default:
return FALSE;
}
}
static gboolean gvnc_has_auth_type(gpointer data)
{
struct gvnc *gvnc = (struct gvnc *)data;
if (gvnc->has_error)
return TRUE;
if (gvnc->auth_type == GVNC_AUTH_INVALID)
return FALSE;
return TRUE;
}
static gboolean gvnc_perform_auth(struct gvnc *gvnc)
{
unsigned int nauth, i;
unsigned int auth[10];
if (gvnc->minor <= 6) {
nauth = 1;
auth[0] = gvnc_read_u32(gvnc);
} else {
nauth = gvnc_read_u8(gvnc);
if (gvnc_has_error(gvnc))
return FALSE;
if (nauth == 0)
return gvnc_check_auth_result(gvnc);
if (nauth > sizeof(auth)) {
gvnc->has_error = TRUE;
return FALSE;
}
for (i = 0 ; i < nauth ; i++)
auth[i] = gvnc_read_u8(gvnc);
}
for (i = 0 ; i < nauth ; i++) {
GVNC_DEBUG("Possible auth %u\n", auth[i]);
}
if (gvnc->has_error || !gvnc->ops.auth_type)
return FALSE;
if (!gvnc->ops.auth_type(gvnc->ops_data, nauth, auth))
gvnc->has_error = TRUE;
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Waiting for auth type\n");
g_condition_wait(gvnc_has_auth_type, gvnc);
if (gvnc->has_error)
return FALSE;
GVNC_DEBUG("Choose auth %u\n", gvnc->auth_type);
if (!gvnc_gather_credentials(gvnc))
return FALSE;
if (gvnc->minor > 6) {
gvnc_write_u8(gvnc, gvnc->auth_type);
gvnc_flush(gvnc);
}
switch (gvnc->auth_type) {
case GVNC_AUTH_NONE:
if (gvnc->minor == 8)
return gvnc_check_auth_result(gvnc);
return TRUE;
case GVNC_AUTH_VNC:
return gvnc_perform_auth_vnc(gvnc);
case GVNC_AUTH_TLS:
if (gvnc->minor < 7)
return FALSE;
return gvnc_perform_auth_tls(gvnc);
case GVNC_AUTH_VENCRYPT:
return gvnc_perform_auth_vencrypt(gvnc);
default:
if (gvnc->ops.auth_unsupported)
gvnc->ops.auth_unsupported (gvnc->ops_data, gvnc->auth_type);
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
struct gvnc *gvnc_new(const struct gvnc_ops *ops, gpointer ops_data)
{
struct gvnc *gvnc = malloc(sizeof(*gvnc));
if (gvnc == NULL)
return NULL;
memset(gvnc, 0, sizeof(*gvnc));
gvnc->fd = -1;
memcpy(&gvnc->ops, ops, sizeof(*ops));
gvnc->ops_data = ops_data;
gvnc->auth_type = GVNC_AUTH_INVALID;
gvnc->auth_subtype = GVNC_AUTH_INVALID;
return gvnc;
}
void gvnc_free(struct gvnc *gvnc)
{
if (!gvnc)
return;
if (gvnc_is_open(gvnc))
gvnc_close(gvnc);
free(gvnc);
gvnc = NULL;
}
void gvnc_close(struct gvnc *gvnc)
{
if (gvnc->tls_session) {
gnutls_bye(gvnc->tls_session, GNUTLS_SHUT_RDWR);
gvnc->tls_session = NULL;
}
if (gvnc->channel) {
g_io_channel_unref(gvnc->channel);
gvnc->channel = NULL;
}
if (gvnc->fd != -1) {
close(gvnc->fd);
gvnc->fd = -1;
}
if (gvnc->host) {
free(gvnc->host);
gvnc->host = NULL;
}
if (gvnc->port) {
free(gvnc->port);
gvnc->port = NULL;
}
if (gvnc->name) {
free(gvnc->name);
gvnc->name = NULL;
}
if (gvnc->cred_username) {
free(gvnc->cred_username);
gvnc->cred_username = NULL;
}
if (gvnc->cred_password) {
free(gvnc->cred_password);
gvnc->cred_password = NULL;
}
if (gvnc->cred_x509_cacert) {
free(gvnc->cred_x509_cacert);
gvnc->cred_x509_cacert = NULL;
}
if (gvnc->cred_x509_cacrl) {
free(gvnc->cred_x509_cacrl);
gvnc->cred_x509_cacrl = NULL;
}
if (gvnc->cred_x509_cert) {
free(gvnc->cred_x509_cert);
gvnc->cred_x509_cert = NULL;
}
if (gvnc->cred_x509_key) {
free(gvnc->cred_x509_key);
gvnc->cred_x509_key = NULL;
}
gvnc->auth_type = GVNC_AUTH_INVALID;
gvnc->auth_subtype = GVNC_AUTH_INVALID;
gvnc->has_error = 0;
}
void gvnc_shutdown(struct gvnc *gvnc)
{
close(gvnc->fd);
gvnc->fd = -1;
gvnc->has_error = 1;
GVNC_DEBUG("Waking up couroutine to shutdown gracefully\n");
g_io_wakeup(&gvnc->wait);
}
gboolean gvnc_is_open(struct gvnc *gvnc)
{
if (!gvnc)
return FALSE;
if (gvnc->fd != -1)
return TRUE;
if (gvnc->host)
return TRUE;
return FALSE;
}
gboolean gvnc_is_initialized(struct gvnc *gvnc)
{
if (!gvnc_is_open(gvnc))
return FALSE;
if (gvnc->name)
return TRUE;
return FALSE;
}
gboolean gvnc_initialize(struct gvnc *gvnc, gboolean shared_flag)
{
int ret;
char version[13];
uint32_t n_name;
gvnc->absolute = 1;
gvnc_read(gvnc, version, 12);
version[12] = 0;
ret = sscanf(version, "RFB %03d.%03d\n", &gvnc->major, &gvnc->minor);
if (ret != 2)
goto fail;
if (gvnc->major != 3)
goto fail;
if (gvnc->minor != 3 &&
gvnc->minor != 4 &&
gvnc->minor != 5 &&
gvnc->minor != 6 &&
gvnc->minor != 7 &&
gvnc->minor != 8)
goto fail;
snprintf(version, 12, "RFB %03d.%03d\n", gvnc->major, gvnc->minor);
gvnc_write(gvnc, version, 12);
gvnc_flush(gvnc);
GVNC_DEBUG("Negotiated protocol %d %d\n", gvnc->major, gvnc->minor);
if (!gvnc_perform_auth(gvnc)) {
GVNC_DEBUG("Auth failed\n");
goto fail;
}
gvnc_write_u8(gvnc, shared_flag); /* shared flag */
gvnc_flush(gvnc);
gvnc->width = gvnc_read_u16(gvnc);
gvnc->height = gvnc_read_u16(gvnc);
gvnc_read_pixel_format(gvnc, &gvnc->fmt);
n_name = gvnc_read_u32(gvnc);
if (n_name > 4096)
goto fail;
gvnc->name = malloc(n_name + 1);
if (gvnc->name == NULL)
goto fail;
gvnc_read(gvnc, gvnc->name, n_name);
gvnc->name[n_name] = 0;
GVNC_DEBUG("Display name '%s'\n", gvnc->name);
gvnc_resize(gvnc, gvnc->width, gvnc->height);
return !gvnc_has_error(gvnc);
fail:
gvnc->has_error = 1;
return !gvnc_has_error(gvnc);
}
gboolean gvnc_open_fd(struct gvnc *gvnc, int fd)
{
int flags;
if (gvnc_is_open(gvnc))
return FALSE;
GVNC_DEBUG("Connecting to FD %d\n", fd);
if ((flags = fcntl(fd, F_GETFL)) < 0)
return FALSE;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return FALSE;
if (!(gvnc->channel = g_io_channel_unix_new(fd)))
return FALSE;
gvnc->fd = fd;
return !gvnc_has_error(gvnc);
}
gboolean gvnc_open_host(struct gvnc *gvnc, const char *host, const char *port)
{
struct addrinfo *ai, *runp, hints;
int ret;
if (gvnc_is_open(gvnc))
return FALSE;
gvnc->host = g_strdup(host);
gvnc->port = g_strdup(port);
GVNC_DEBUG("Resolving host %s %s\n", host, port);
memset (&hints, '\0', sizeof (hints));
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if ((ret = getaddrinfo(host, port, &hints, &ai)) != 0)
return FALSE;
runp = ai;
while (runp != NULL) {
int flags, fd;
GIOChannel *chan;
if ((fd = socket(runp->ai_family, runp->ai_socktype,
runp->ai_protocol)) < 0)
break;
GVNC_DEBUG("Trying socket %d\n", fd);
if ((flags = fcntl(fd, F_GETFL)) < 0) {
close(fd);
break;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
close(fd);
break;
}
if (!(chan = g_io_channel_unix_new(fd))) {
close(fd);
break;
}
reconnect:
/* FIXME: Better handle EINPROGRESS/EISCONN return values,
as explained in connect(2) man page */
if ( (connect(fd, runp->ai_addr, runp->ai_addrlen) == 0) || errno == EISCONN) {
gvnc->channel = chan;
gvnc->fd = fd;
freeaddrinfo(ai);
return !gvnc_has_error(gvnc);
}
if (errno == EINPROGRESS) {
g_io_wait(chan, G_IO_OUT|G_IO_ERR|G_IO_HUP);
goto reconnect;
} else if (errno != ECONNREFUSED &&
errno != EHOSTUNREACH) {
g_io_channel_unref(chan);
close(fd);
break;
}
close(fd);
g_io_channel_unref(chan);
runp = runp->ai_next;
}
freeaddrinfo (ai);
return FALSE;
}
gboolean gvnc_set_auth_type(struct gvnc *gvnc, unsigned int type)
{
GVNC_DEBUG("Requested auth type %u\n", type);
if (gvnc->auth_type != GVNC_AUTH_INVALID) {
gvnc->has_error = TRUE;
return !gvnc_has_error(gvnc);
}
if (type != GVNC_AUTH_NONE &&
type != GVNC_AUTH_VNC &&
type != GVNC_AUTH_TLS &&
type != GVNC_AUTH_VENCRYPT) {
if (gvnc->ops.auth_unsupported)
gvnc->ops.auth_unsupported (gvnc->ops_data, type);
gvnc->has_error = TRUE;
return !gvnc_has_error(gvnc);
}
gvnc->auth_type = type;
gvnc->auth_subtype = GVNC_AUTH_INVALID;
return !gvnc_has_error(gvnc);
}
gboolean gvnc_set_auth_subtype(struct gvnc *gvnc, unsigned int type)
{
GVNC_DEBUG("Requested auth subtype %d\n", type);
if (gvnc->auth_type != GVNC_AUTH_VENCRYPT &&
gvnc->auth_type != GVNC_AUTH_TLS) {
gvnc->has_error = TRUE;
return !gvnc_has_error(gvnc);
}
if (gvnc->auth_subtype != GVNC_AUTH_INVALID) {
gvnc->has_error = TRUE;
return !gvnc_has_error(gvnc);
}
gvnc->auth_subtype = type;
return !gvnc_has_error(gvnc);
}
gboolean gvnc_set_credential_password(struct gvnc *gvnc, const char *password)
{
GVNC_DEBUG("Set password credential\n");
if (gvnc->cred_password)
free(gvnc->cred_password);
if (!(gvnc->cred_password = strdup(password))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_credential_username(struct gvnc *gvnc, const char *username)
{
GVNC_DEBUG("Set username credential %s\n", username);
if (gvnc->cred_username)
free(gvnc->cred_username);
if (!(gvnc->cred_username = strdup(username))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_credential_x509_cacert(struct gvnc *gvnc, const char *file)
{
GVNC_DEBUG("Set x509 cacert %s\n", file);
if (gvnc->cred_x509_cacert)
free(gvnc->cred_x509_cacert);
if (!(gvnc->cred_x509_cacert = strdup(file))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_credential_x509_cacrl(struct gvnc *gvnc, const char *file)
{
GVNC_DEBUG("Set x509 cacrl %s\n", file);
if (gvnc->cred_x509_cacrl)
free(gvnc->cred_x509_cacrl);
if (!(gvnc->cred_x509_cacrl = strdup(file))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_credential_x509_key(struct gvnc *gvnc, const char *file)
{
GVNC_DEBUG("Set x509 key %s\n", file);
if (gvnc->cred_x509_key)
free(gvnc->cred_x509_key);
if (!(gvnc->cred_x509_key = strdup(file))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_credential_x509_cert(struct gvnc *gvnc, const char *file)
{
GVNC_DEBUG("Set x509 cert %s\n", file);
if (gvnc->cred_x509_cert)
free(gvnc->cred_x509_cert);
if (!(gvnc->cred_x509_cert = strdup(file))) {
gvnc->has_error = TRUE;
return FALSE;
}
return TRUE;
}
gboolean gvnc_set_local(struct gvnc *gvnc, struct gvnc_framebuffer *fb)
{
int i, j, n;
int depth;
memcpy(&gvnc->local, fb, sizeof(*fb));
if (fb->bpp == (gvnc->fmt.bits_per_pixel / 8) &&
fb->red_mask == gvnc->fmt.red_max &&
fb->green_mask == gvnc->fmt.green_max &&
fb->blue_mask == gvnc->fmt.blue_max &&
fb->red_shift == gvnc->fmt.red_shift &&
fb->green_shift == gvnc->fmt.green_shift &&
fb->blue_shift == gvnc->fmt.blue_shift &&
__BYTE_ORDER == gvnc->fmt.byte_order)
gvnc->perfect_match = TRUE;
else
gvnc->perfect_match = FALSE;
depth = gvnc->fmt.depth;
if (depth == 32)
depth = 24;
gvnc->rm = gvnc->local.red_mask & gvnc->fmt.red_max;
gvnc->gm = gvnc->local.green_mask & gvnc->fmt.green_max;
gvnc->bm = gvnc->local.blue_mask & gvnc->fmt.blue_max;
GVNC_DEBUG("Mask local: %3d %3d %3d\n"
" remote: %3d %3d %3d\n"
" merged: %3d %3d %3d\n",
gvnc->local.red_mask, gvnc->local.green_mask, gvnc->local.blue_mask,
gvnc->fmt.red_max, gvnc->fmt.green_max, gvnc->fmt.blue_max,
gvnc->rm, gvnc->gm, gvnc->bm);
/* Setup shifts assuming matched bpp (but not neccessarily match rgb order)*/
gvnc->rrs = gvnc->fmt.red_shift;
gvnc->grs = gvnc->fmt.green_shift;
gvnc->brs = gvnc->fmt.blue_shift;
gvnc->rls = gvnc->local.red_shift;
gvnc->gls = gvnc->local.green_shift;
gvnc->bls = gvnc->local.blue_shift;
/* This adjusts for server/client endianness mismatch */
if (__BYTE_ORDER != gvnc->fmt.byte_order) {
gvnc->rrs = gvnc->fmt.bits_per_pixel - gvnc->rrs - (gvnc->fmt.bits_per_pixel - gvnc->fmt.depth);
gvnc->grs = gvnc->fmt.bits_per_pixel - gvnc->grs - (gvnc->fmt.bits_per_pixel - gvnc->fmt.depth);
gvnc->brs = gvnc->fmt.bits_per_pixel - gvnc->brs - (gvnc->fmt.bits_per_pixel - gvnc->fmt.depth);
GVNC_DEBUG("Flipped shifts red: %3d, green: %3d, blue: %3d\n",
gvnc->rrs, gvnc->grs, gvnc->brs);
}
/* This adjusts for remote having more bpp than local */
for (n = gvnc->fmt.red_max; n > gvnc->local.red_mask ; n>>= 1)
gvnc->rrs++;
for (n = gvnc->fmt.green_max; n > gvnc->local.green_mask ; n>>= 1)
gvnc->grs++;
for (n = gvnc->fmt.blue_max; n > gvnc->local.blue_mask ; n>>= 1)
gvnc->brs++;
/* This adjusts for remote having less bpp than remote */
for (n = gvnc->local.red_mask ; n > gvnc->fmt.red_max ; n>>= 1)
gvnc->rls++;
for (n = gvnc->local.green_mask ; n > gvnc->fmt.green_max ; n>>= 1)
gvnc->gls++;
for (n = gvnc->local.blue_mask ; n > gvnc->fmt.blue_max ; n>>= 1)
gvnc->bls++;
GVNC_DEBUG("Pixel shifts\n right: %3d %3d %3d\n left: %3d %3d %3d\n",
gvnc->rrs, gvnc->grs, gvnc->brs,
gvnc->rls, gvnc->gls, gvnc->bls);
i = gvnc->fmt.bits_per_pixel / 8;
j = gvnc->local.bpp;
if (i == 4) i = 3;
if (j == 4) j = 3;
gvnc->blt = gvnc_blt_table[i - 1][j - 1];
gvnc->hextile = gvnc_hextile_table[i - 1][j - 1];
if (gvnc->perfect_match)
gvnc->blt = gvnc_blt_fast;
return !gvnc_has_error(gvnc);
}
gboolean gvnc_shared_memory_enabled(struct gvnc *gvnc)
{
return gvnc->shared_memory_enabled;
}
const char *gvnc_get_name(struct gvnc *gvnc)
{
return gvnc->name;
}
int gvnc_get_width(struct gvnc *gvnc)
{
return gvnc->width;
}
int gvnc_get_height(struct gvnc *gvnc)
{
return gvnc->height;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* tab-width: 8
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1