From be9380312963701b1bedac9b6c28bafdc2a7ada6 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 21 Jan 2013 12:28:14 +0000 Subject: [PATCH] Intitial implementation of seamless connection sharing: - Implementation of ctrl functionality were slaves can call a method into existing master process, more information is found in doc/ctrl.txt - Implementation of new seamless SPAWN functionality so which is used by the ctrl to spawn processes in a seamless rdp session out of process. git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/rdesktop/trunk@1689 423420c4-83ab-492f-b58f-81f9feb106b5 --- Makefile.in | 4 +- ctrl.c | 518 +++++++++++++++++++++++++++++++++++++++++++++++++ doc/ctrl.txt | 61 ++++++ doc/rdesktop.1 | 4 + proto.h | 16 ++ rdesktop.c | 89 +++------ seamless.c | 20 +- utils.c | 224 +++++++++++++++++++++ xwin.c | 6 + 9 files changed, 875 insertions(+), 67 deletions(-) create mode 100644 ctrl.c create mode 100644 doc/ctrl.txt create mode 100644 utils.c diff --git a/Makefile.in b/Makefile.in index 52f1612..bc13275 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,8 +28,8 @@ SOUNDOBJ = @SOUNDOBJ@ SCARDOBJ = @SCARDOBJ@ CREDSSPOBJ = @CREDSSPOBJ@ -RDPOBJ = tcp.o asn.o iso.o mcs.o secure.o licence.o rdp.o orders.o bitmap.o cache.o rdp5.o channels.o rdpdr.o serial.o printer.o disk.o parallel.o printercache.o mppc.o pstcache.o lspci.o seamless.o ssl.o -X11OBJ = rdesktop.o xwin.o xkeymap.o ewmhints.o xclip.o cliprdr.o +RDPOBJ = tcp.o asn.o iso.o mcs.o secure.o licence.o rdp.o orders.o bitmap.o cache.o rdp5.o channels.o rdpdr.o serial.o printer.o disk.o parallel.o printercache.o mppc.o pstcache.o lspci.o seamless.o ssl.o utils.o +X11OBJ = rdesktop.o xwin.o xkeymap.o ewmhints.o xclip.o cliprdr.o ctrl.o VNCOBJ = vnc/rdp2vnc.o vnc/vnc.o vnc/xkeymap.o vnc/x11stubs.o .PHONY: all diff --git a/ctrl.c b/ctrl.c new file mode 100644 index 0000000..5719a6a --- /dev/null +++ b/ctrl.c @@ -0,0 +1,518 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Master/Slave remote controlling + Copyright 2013 Henrik Andersson for Cendio AB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "rdesktop.h" +#include "ssl.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CTRL_LINEBUF_SIZE 1024 +#define CTRL_RESULT_SIZE 32 +#define RDESKTOP_CTRLSOCK_STORE "/.local/var/run/rdesktop" + +#define CTRL_HASH_FLAG_SEAMLESS 1 + +#define ERR_RESULT_OK 0x00 +#define ERR_RESULT_NO_SUCH_COMMAND 0xffffffff + +extern RD_BOOL g_seamless_rdp; +extern uint8 g_static_rdesktop_salt_16[]; + +static RD_BOOL _ctrl_is_slave; +static int ctrlsock; +static char ctrlsock_name[PATH_MAX]; +static struct _ctrl_slave_t *_ctrl_slaves; + +#define CMD_SEAMLESS_SPAWN "seamless.spawn" + +typedef struct _ctrl_slave_t +{ + struct _ctrl_slave_t *prev, *next; + int sock; + char linebuf[CTRL_LINEBUF_SIZE]; +} _ctrl_slave_t; + + +static void +_ctrl_slave_new(int sock) +{ + _ctrl_slave_t *it, *ns; + + /* initialize new slave list item */ + ns = (_ctrl_slave_t *) xmalloc(sizeof(_ctrl_slave_t)); + memset(ns, 0, sizeof(_ctrl_slave_t)); + ns->sock = sock; + + /* append new slave to end of list */ + it = _ctrl_slaves; + + /* find last element in list */ + while (it && it->next) + it = it->next; + + /* if last found append new */ + if (it) + { + it->next = ns; + ns->prev = it; + } + else + { + /* no elemnts in list, lets add first */ + _ctrl_slaves = ns; + } +} + +static void +_ctrl_slave_disconnect(int sock) +{ + _ctrl_slave_t *it; + + if (!_ctrl_slaves) + return; + + it = _ctrl_slaves; + + /* find slave with sock */ + while (it->next && it->sock != sock) + it = it->next; + + if (it->sock == sock) + { + /* shutdown socket */ + shutdown(sock, SHUT_RDWR); + close(sock); + + /* remove item from list */ + if (it == _ctrl_slaves) + { + if (it->next) + _ctrl_slaves = it->next; + else + _ctrl_slaves = NULL; + } + + if (it->prev) + { + (it->prev)->next = it->next; + if (it->next) + (it->next)->prev = it->prev; + } + else if (it->next) + (it->next)->prev = NULL; + + xfree(it); + + } +} + +static void +_ctrl_command_result(_ctrl_slave_t * slave, int result) +{ + char buf[64] = { 0 }; + + /* translate and send result code back to client */ + if (result == 0) + send(slave->sock, "OK\n", 3, 0); + else + { + snprintf(buf, 64, "ERROR %x\n", result); + send(slave->sock, buf, strlen(buf), 0); + } +} + +static void +_ctrl_dispatch_command(_ctrl_slave_t * slave) +{ + char *p; + char *cmd; + unsigned int res; + + /* unescape linebuffer */ + cmd = utils_string_unescape(slave->linebuf); + if (strncmp(cmd, CMD_SEAMLESS_SPAWN " ", strlen(CMD_SEAMLESS_SPAWN) + 1) == 0) + { + /* process seamless spawn request */ + p = strstr(cmd, "seamlessrdpshell.exe"); + if (p) + p += strlen("seamlessrdpshell.exe") + 1; + else + p = cmd + strlen(CMD_SEAMLESS_SPAWN) + 1; + + res = ERR_RESULT_OK; + + if (seamless_send_spawn(p) == (unsigned int) -1) + res = 1; + } + else + { + res = ERR_RESULT_NO_SUCH_COMMAND; + } + xfree(cmd); + + _ctrl_command_result(slave, res); +} + +static RD_BOOL +_ctrl_verify_unix_socket() +{ + int s, len; + struct sockaddr_un saun; + + memset(&saun, 0, sizeof(struct sockaddr_un)); + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + perror("Error creating ctrl client socket: socket()"); + exit(1); + } + + saun.sun_family = AF_UNIX; + strcpy(saun.sun_path, ctrlsock_name); + len = sizeof(saun.sun_family) + strlen(saun.sun_path); + + /* test connection */ + if (connect(s, (struct sockaddr *) &saun, len) != 0) + return False; + + shutdown(s, SHUT_RDWR); + close(s); + return True; +} + + +static void +_ctrl_create_hash(const char *user, const char *domain, const char *host, char *hash, size_t hsize) +{ + RDSSL_SHA1 sha1; + uint8 out[20], delim; + uint16 version; + uint32 flags; + + /* version\0user\0domain\0host\0flags */ + flags = 0; + delim = '\0'; + version = 0x0100; + + if (g_seamless_rdp) + flags = CTRL_HASH_FLAG_SEAMLESS; + + rdssl_sha1_init(&sha1); + rdssl_sha1_update(&sha1, (uint8 *) & version, sizeof(version)); + rdssl_sha1_update(&sha1, &delim, 1); + + if (user) + rdssl_sha1_update(&sha1, (uint8 *) user, strlen(user)); + rdssl_sha1_update(&sha1, &delim, 1); + + if (domain) + rdssl_sha1_update(&sha1, (uint8 *) domain, strlen(domain)); + rdssl_sha1_update(&sha1, &delim, 1); + + if (host) + rdssl_sha1_update(&sha1, (uint8 *) host, strlen(host)); + rdssl_sha1_update(&sha1, &delim, 1); + + rdssl_sha1_update(&sha1, (uint8 *) & flags, sizeof(flags)); + rdssl_sha1_final(&sha1, out); + + sec_hash_to_string(hash, hsize, out, sizeof(out)); +} + + +/** Initialize ctrl + Ret values: <0 failure, 0 master, 1 client + */ +int +ctrl_init(const char *user, const char *domain, const char *host) +{ + struct stat st; + struct sockaddr_un saun; + char hash[41], path[PATH_MAX]; + char *home; + + /* check if ctrl already initialized */ + if (ctrlsock != 0 || _ctrl_is_slave) + return 0; + + home = getenv("HOME"); + if (home == NULL) + { + return -1; + } + + /* get uniq hash for ctrlsock name */ + _ctrl_create_hash(user, domain, host, hash, 41); + snprintf(ctrlsock_name, PATH_MAX, "%s" RDESKTOP_CTRLSOCK_STORE "/%s.ctl", home, hash); + ctrlsock_name[sizeof(ctrlsock_name) - 1] = '\0'; + + /* make sure that ctrlsock store path exists */ + snprintf(path, PATH_MAX, "%s" RDESKTOP_CTRLSOCK_STORE, home); + path[sizeof(path) - 1] = '\0'; + if (utils_mkdir_p(path, 0700) == -1) + { + perror(path); + return -1; + } + + /* check if ctrl socket already exist then this process becomes a client */ + if (stat(ctrlsock_name, &st) == 0) + { + /* verify that unix socket is not stale */ + if (_ctrl_verify_unix_socket() == True) + { + _ctrl_is_slave = True; + return 1; + } + else + { + unlink(ctrlsock_name); + } + } + + /* setup ctrl socket and start listening for connections */ + if ((ctrlsock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + perror("Error creating ctrl socket:"); + exit(1); + } + + /* bind and start listening on server socket */ + memset(&saun, 0, sizeof(struct sockaddr_un)); + saun.sun_family = AF_UNIX; + strncpy(saun.sun_path, ctrlsock_name, sizeof(saun.sun_path)); + if (bind(ctrlsock, (struct sockaddr *) &saun, sizeof(struct sockaddr_un)) < 0) + { + perror("Error binding ctrl socket:"); + exit(1); + } + + if (listen(ctrlsock, 5) < 0) + { + perror("Error listening on socket:"); + exit(1); + } + + /* add ctrl cleanup func to exit hooks */ + atexit(ctrl_cleanup); + + return 0; +} + +void +ctrl_cleanup() +{ + if (ctrlsock) + { + close(ctrlsock); + unlink(ctrlsock_name); + } +} + +RD_BOOL +ctrl_is_slave() +{ + return _ctrl_is_slave; +} + + +void +ctrl_add_fds(int *n, fd_set * rfds) +{ + _ctrl_slave_t *it; + if (ctrlsock == 0) + return; + + FD_SET(ctrlsock, rfds); + *n = MAX(*n, ctrlsock); + + + /* add connected slaves to fd set */ + it = _ctrl_slaves; + while (it) + { + FD_SET(it->sock, rfds); + *n = MAX(*n, it->sock); + it = it->next; + } +} + +void +ctrl_check_fds(fd_set * rfds, fd_set * wfds) +{ + int ns, res, offs; + struct sockaddr_un fsaun; + socklen_t fromlen; + _ctrl_slave_t *it; + + if (ctrlsock == 0) + return; + + memset(&fsaun, 0, sizeof(struct sockaddr_un)); + + /* check if we got any connections on server socket */ + if (FD_ISSET(ctrlsock, rfds)) + { + FD_CLR(ctrlsock, rfds); + fromlen = sizeof(fsaun); + ns = accept(ctrlsock, (struct sockaddr *) &fsaun, &fromlen); + if (ns < 0) + { + perror("server: accept()"); + exit(1); + } + + _ctrl_slave_new(ns); + return; + } + + /* check if any of our slaves fds has data */ + it = _ctrl_slaves; + while (it) + { + if (FD_ISSET(it->sock, rfds)) + { + offs = strlen(it->linebuf); + res = recv(it->sock, it->linebuf + offs, CTRL_LINEBUF_SIZE - offs, 0); + FD_CLR(it->sock, rfds); + + /* linebuffer full let's disconnect slave */ + if (it->linebuf[CTRL_LINEBUF_SIZE - 1] != '\0' && + it->linebuf[CTRL_LINEBUF_SIZE - 1] != '\n') + { + _ctrl_slave_disconnect(it->sock); + break; + } + + if (res >= 0) + { + /* Check if we got full command line */ + char *p; + if ((p = strchr(it->linebuf, '\n')) == NULL) + continue; + + /* iterate over string and check against escaped \n */ + while (p) + { + /* Check if newline is escaped */ + if (p > it->linebuf && *(p - 1) != '\\') + break; + p = strchr(p + 1, '\n'); + } + + /* If we havent found an nonescaped \n we need more data */ + if (p == NULL) + continue; + + /* strip new linebuf and dispatch command */ + *p = '\0'; + _ctrl_dispatch_command(it); + memset(it->linebuf, 0, CTRL_LINEBUF_SIZE); + } + else + { + /* Peer disconnected or socket error */ + _ctrl_slave_disconnect(it->sock); + break; + } + } + it = it->next; + } +} + +#if HAVE_ICONV +extern char g_codepage[16]; +#endif + +int +ctrl_send_command(const char *cmd, const char *arg) +{ + FILE *fp; + struct sockaddr_un saun; + int s, len, index, ret; + char data[CTRL_LINEBUF_SIZE], tmp[CTRL_LINEBUF_SIZE]; + char result[CTRL_RESULT_SIZE], c, *escaped; + + escaped = NULL; + + if (!_ctrl_is_slave) + return -1; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + perror("Error creating ctrl client socket: socket()"); + exit(1); + } + + memset(&saun, 0, sizeof(struct sockaddr_un)); + saun.sun_family = AF_UNIX; + strcpy(saun.sun_path, ctrlsock_name); + len = sizeof(saun.sun_family) + strlen(saun.sun_path); + + if (connect(s, (struct sockaddr *) &saun, len) < 0) + { + perror("Error connecting to ctrl socket: connect()"); + exit(1); + } + + /* Bundle cmd and argument into string, convert to UTF-8 if needed */ + snprintf(data, CTRL_LINEBUF_SIZE, "%s %s", cmd, arg); + ret = utils_locale_to_utf8(data, strlen(data), tmp, CTRL_LINEBUF_SIZE - 1); + + if (ret != 0) + goto bail_out; + + /* escape the utf-8 string */ + escaped = utils_string_escape(tmp); + if ((strlen(escaped) + 1) > CTRL_LINEBUF_SIZE - 1) + goto bail_out; + + /* send escaped utf-8 command to master */ + send(s, escaped, strlen(escaped), 0); + send(s, "\n", 1, 0); + + /* read result from master */ + fp = fdopen(s, "r"); + index = 0; + while ((c = fgetc(fp)) != EOF && index < CTRL_RESULT_SIZE && c != '\n') + { + result[index] = c; + index++; + } + result[index - 1] = '\0'; + + if (strncmp(result, "ERROR ", 6) == 0) + { + if (sscanf(result, "ERROR %d", &ret) != 1) + ret = -1; + } + + bail_out: + xfree(escaped); + shutdown(s, SHUT_RDWR); + close(s); + + return ret; +} diff --git a/doc/ctrl.txt b/doc/ctrl.txt new file mode 100644 index 0000000..879d9ed --- /dev/null +++ b/doc/ctrl.txt @@ -0,0 +1,61 @@ +TODO +---- + +Overview +======== + +The protocol is a UTF-8 line based bidirectional protocol were a slave sends +a method with optional argument to the master and a result of operation is +returned to the slave in a synchronous send recv operation. + + +A method call from Slave to the Master have the following syntax: + + METHOD [ARG1[ ARG2 [...]]] + +Argument string sent for a method call should be escaped as descibed below under +String escaping section. + + +A result from Master to the Slave have the following syntax: + + "OK" || "ERROR" + +Result CODE is specific to the method implementation except for 0xffffffff which +is internal to the protocol and means that method does not exist. + + +One line may not exceed 1024 bytes, including newline. + + +String escaping +=============== +Percentage sign has been choosen as escaping character. + +The rules for escaping are: + + Characters < 32 and % are escaped to %xx where xx is the byte value of the + escaped character. + + +Implemented methods +======================= + +seamless.spawn +-------------- + +Spawns a new windows command in the current seamless channel of the master. + +Syntax: + seamless.spawn + +Spawns a seamless process in the current seamless channel of the master with COMMANDLINE. + +Errorcodes: + + 0x1 Master does not have a seamless rdp channel established. + + + + + diff --git a/doc/rdesktop.1 b/doc/rdesktop.1 index 6c18672..95854d6 100644 --- a/doc/rdesktop.1 +++ b/doc/rdesktop.1 @@ -86,6 +86,10 @@ toggled at any time using Ctrl-Alt-Enter. Force the server to send screen updates as bitmaps rather than using higher-level drawing operations. .TP +.BR "-t" +Disable use of remote control. This will disable features like seamless connection +sharing. +.TP .BR "-A" Enable SeamlessRDP. In this mode, rdesktop creates a X11 window for each window on the server side. This mode requires the SeamlessRDP diff --git a/proto.h b/proto.h index 4b50a43..903814a 100644 --- a/proto.h +++ b/proto.h @@ -24,6 +24,12 @@ extern "C" { #endif /* *INDENT-ON* */ +/* utils.c */ +char *utils_string_escape(const char *str); +char *utils_string_unescape(const char *str); +int utils_locale_to_utf8(const char *src, size_t is, char *dest, size_t os); +int utils_mkdir_safe(const char *path, int mask); +int utils_mkdir_p(const char *path, int mask); /* bitmap.c */ RD_BOOL bitmap_decompress(uint8 * output, int width, int height, uint8 * input, int size, int Bpp); /* cache.c */ @@ -57,6 +63,14 @@ void cliprdr_send_data_request(uint32 format); void cliprdr_send_data(uint8 * data, uint32 length); void cliprdr_set_mode(const char *optarg); RD_BOOL cliprdr_init(void); +/* ctrl.c */ +int ctrl_init(const char *user, const char *domain, const char *host); +void ctrl_cleanup(); +RD_BOOL ctrl_is_slave(); +int ctrl_send_command(const char *cmd, const char *args); +void ctrl_add_fds(int *n, fd_set * rfds); +void ctrl_check_fds(fd_set * rfds, fd_set * wfds); + /* disk.c */ int disk_enum_devices(uint32 * id, char *optarg); RD_NTSTATUS disk_query_information(RD_NTHANDLE handle, uint32 info_class, STREAM out); @@ -327,6 +341,8 @@ void seamless_select_timeout(struct timeval *tv); unsigned int seamless_send_zchange(unsigned long id, unsigned long below, unsigned long flags); unsigned int seamless_send_focus(unsigned long id, unsigned long flags); unsigned int seamless_send_destroy(unsigned long id); +unsigned int seamless_send_spawn(char *cmd); + /* scard.c */ void scard_lock(int lock); void scard_unlock(int lock); diff --git a/rdesktop.c b/rdesktop.c index 8dd51a0..8f28e1a 100644 --- a/rdesktop.c +++ b/rdesktop.c @@ -3,7 +3,7 @@ Entrypoint and utility functions Copyright (C) Matthew Chapman 1999-2008 Copyright 2002-2011 Peter Astrand for Cendio AB - Copyright 2010-2011 Henrik Andersson for Cendio AB + Copyright 2010-2013 Henrik Andersson for Cendio AB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -85,6 +85,7 @@ RD_BOOL g_sendmotion = True; RD_BOOL g_bitmap_cache = True; RD_BOOL g_bitmap_cache_persist_enable = False; RD_BOOL g_bitmap_cache_precache = True; +RD_BOOL g_use_ctrl = True; RD_BOOL g_encryption = True; RD_BOOL g_packet_encryption = True; RD_BOOL g_desktop_save = True; /* desktop save order */ @@ -178,6 +179,7 @@ usage(char *program) fprintf(stderr, " -K: keep window manager key bindings\n"); fprintf(stderr, " -S: caption button size (single application mode)\n"); fprintf(stderr, " -T: window title\n"); + fprintf(stderr, " -t: disable use of remote ctrl\n"); fprintf(stderr, " -N: enable numlock syncronization\n"); fprintf(stderr, " -X: embed into another window with a given id.\n"); fprintf(stderr, " -a: connection colour depth\n"); @@ -580,6 +582,9 @@ main(int argc, char *argv[]) flags |= RDP_LOGON_PASSWORD_IS_SC_PIN; break; #endif + case 't': + g_use_ctrl = False; + break; case 'n': STRNCPY(g_hostname, optarg, sizeof(g_hostname)); @@ -979,6 +984,24 @@ main(int argc, char *argv[]) return EX_OK; #else + /* Only startup ctrl functionality is seamless are used for now. */ + if (g_use_ctrl && g_seamless_rdp) + { + if (ctrl_init(server, domain, g_username) < 0) + { + error("Failed to initialize ctrl mode."); + exit(1); + } + + if (ctrl_is_slave()) + { + fprintf(stdout, + "rdesktop in slave mode sending command to master process.\n"); + + return ctrl_send_command("seamless.spawn", shell); + } + } + if (!ui_init()) return EX_OSERR; @@ -1510,68 +1533,6 @@ l_to_a(long N, int base) return ret; } -static int -safe_mkdir(const char *path, int mask) -{ - int res = 0; - struct stat st; - - res = stat(path, &st); - if (res == -1) - return mkdir(path, mask); - - if (!S_ISDIR(st.st_mode)) - { - errno = EEXIST; - return -1; - } - - return 0; -} - -static int -mkdir_p(const char *path, int mask) -{ - int res; - char *ptok; - char pt[PATH_MAX]; - char bp[PATH_MAX]; - - if (!path || strlen(path) == 0) - { - errno = EINVAL; - return -1; - } - if (strlen(path) > PATH_MAX) - { - errno = E2BIG; - return -1; - } - - res = 0; - pt[0] = bp[0] = '\0'; - strcpy(bp, path); - - ptok = strtok(bp, "/"); - if (ptok == NULL) - return safe_mkdir(path, mask); - - do - { - if (ptok != bp) - strcat(pt, "/"); - - strcat(pt, ptok); - res = safe_mkdir(pt, mask); - if (res != 0) - return res; - - } - while ((ptok = strtok(NULL, "/")) != NULL); - - return 0; -} - int load_licence(unsigned char **data) { @@ -1626,7 +1587,7 @@ save_licence(unsigned char *data, int length) snprintf(path, PATH_MAX, "%s" RDESKTOP_LICENSE_STORE, home); path[sizeof(path) - 1] = '\0'; - if (mkdir_p(path, 0700) == -1) + if (utils_mkdir_p(path, 0700) == -1) { perror(path); return; diff --git a/seamless.c b/seamless.c index d907d8f..5496204 100644 --- a/seamless.c +++ b/seamless.c @@ -3,6 +3,7 @@ Seamless Windows support Copyright 2005-2008 Peter Astrand for Cendio AB Copyright 2007-2008 Pierre Ossman for Cendio AB + Copyright 2013 Henrik Andersson for Cendio AB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -411,7 +412,7 @@ seamless_send(const char *command, const char *format, ...) STREAM s; size_t len; va_list argp; - char buf[1025]; + char *escaped, buf[1025]; len = snprintf(buf, sizeof(buf) - 1, "%s,%u,", command, seamless_serial); @@ -423,6 +424,11 @@ seamless_send(const char *command, const char *format, ...) assert(len < (sizeof(buf) - 1)); + escaped = utils_string_escape(buf); + len = snprintf(buf, sizeof(buf), "%s", escaped); + free(escaped); + assert(len < (sizeof(buf) - 1)); + buf[len] = '\n'; buf[len + 1] = '\0'; @@ -515,3 +521,15 @@ seamless_send_destroy(unsigned long id) { return seamless_send("DESTROY", "0x%08lx", id); } + +unsigned int +seamless_send_spawn(char *cmdline) +{ + unsigned int res; + if (!g_seamless_rdp) + return (unsigned int) -1; + + res = seamless_send("SPAWN", cmdline); + + return res; +} diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..cb42fd5 --- /dev/null +++ b/utils.c @@ -0,0 +1,224 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Generic utility functions + Copyright 2013 Henrik Andersson for Cendio AB + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#ifdef HAVE_ICONV_H +#include +#endif +#include "rdesktop.h" + + +#ifdef HAVE_ICONV +extern char g_codepage[16]; +static RD_BOOL g_iconv_works = True; +#endif + + + +char * +utils_string_escape(const char *str) +{ + const char *p; + char *pe, *e, esc[4]; + size_t es; + int cnt; + + /* count indices */ + cnt = 0; + p = str; + while (*(p++) != '\0') + if ((unsigned char) *p < 32 || *p == '%') + cnt++; + + /* if no characters needs escaping return copy of str */ + if (cnt == 0) + return strdup(str); + + /* allocate new mem for result */ + es = strlen(str) + (cnt * 3) + 1; + pe = e = xmalloc(es); + memset(e, 0, es); + p = str; + while (*p != '\0') + { + if ((unsigned char) *p < 32 || *p == '%') + { + snprintf(esc, 4, "%%%02X", *p); + memcpy(pe, esc, 3); + pe += 3; + } + else + { + *pe = *p; + pe++; + } + + p++; + } + + return e; +} + +char * +utils_string_unescape(const char *str) +{ + char *ns, *ps, *pd, c; + + ns = xmalloc(strlen(str) + 1); + memcpy(ns, str, strlen(str) + 1); + ps = pd = ns; + + while (*ps != '\0') + { + /* check if found escaped character */ + if (ps[0] == '%') + { + if (sscanf(ps, "%%%2hhX", &c) == 1) + { + pd[0] = c; + ps += 3; + pd++; + continue; + } + } + + /* just copy over the char */ + *pd = *ps; + ps++; + pd++; + } + pd[0] = '\0'; + + return ns; +} + +int +utils_mkdir_safe(const char *path, int mask) +{ + int res = 0; + struct stat st; + + res = stat(path, &st); + if (res == -1) + return mkdir(path, mask); + + if (!S_ISDIR(st.st_mode)) + { + errno = EEXIST; + return -1; + } + + return 0; +} + +int +utils_mkdir_p(const char *path, int mask) +{ + int res; + char *ptok; + char pt[PATH_MAX]; + char bp[PATH_MAX]; + + if (!path || strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (strlen(path) > PATH_MAX) + { + errno = E2BIG; + return -1; + } + + res = 0; + pt[0] = bp[0] = '\0'; + strcpy(bp, path); + + ptok = strtok(bp, "/"); + if (ptok == NULL) + return utils_mkdir_safe(path, mask); + + do + { + if (ptok != bp) + strcat(pt, "/"); + + strcat(pt, ptok); + res = utils_mkdir_safe(pt, mask); + if (res != 0) + return res; + + } + while ((ptok = strtok(NULL, "/")) != NULL); + + return 0; +} + +/* Convert from system locale string to utf-8 */ +int +utils_locale_to_utf8(const char *src, size_t is, char *dest, size_t os) +{ +#ifdef HAVE_ICONV + static iconv_t *iconv_h = (iconv_t) - 1; + if (strncmp(g_codepage, "UTF-8", strlen("UTF-8")) == 0) + goto pass_trough_as_is; + + if (g_iconv_works == False) + goto pass_trough_as_is; + + /* if not already initialize */ + if (iconv_h == (iconv_t) - 1) + { + if ((iconv_h = iconv_open("UTF-8", g_codepage)) == (iconv_t) - 1) + { + warning("utils_string_to_utf8: iconv_open[%s -> %s] fail %p\n", + g_codepage, "UTF-8", iconv_h); + + g_iconv_works = False; + goto pass_trough_as_is; + } + } + + /* convert string */ + if (iconv(iconv_h, (ICONV_CONST char **) &src, &is, &dest, &os) == (size_t) - 1) + { + iconv_close(iconv_h); + iconv_h = (iconv_t) - 1; + warning("utils_string_to_utf8: iconv(1) fail, errno %d\n", errno); + + g_iconv_works = False; + goto pass_trough_as_is; + } + + /* Out couldn't hold the entire convertion */ + if (is != 0) + return -1; + +#endif + pass_trough_as_is: + /* can dest hold strcpy of src */ + if (os < (strlen(src) + 1)) + return -1; + + memcpy(dest, src, strlen(src) + 1); + return 0; +} diff --git a/xwin.c b/xwin.c index da812f8..a33b011 100644 --- a/xwin.c +++ b/xwin.c @@ -4,6 +4,7 @@ Copyright (C) Matthew Chapman 1999-2008 Copyright 2007-2008 Pierre Ossman for Cendio AB Copyright 2002-2011 Peter Astrand for Cendio AB + Copyright 2012-2013 Henrik Andersson for Cendio AB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -2668,6 +2669,9 @@ ui_select(int rdp_socket) rdpdr_add_fds(&n, &rfds, &wfds, &tv, &s_timeout); seamless_select_timeout(&tv); + /* add ctrl slaves handles */ + ctrl_add_fds(&n, &rfds); + n++; switch (select(n, &rfds, &wfds, NULL, &tv)) @@ -2692,6 +2696,8 @@ ui_select(int rdp_socket) rdpdr_check_fds(&rfds, &wfds, (RD_BOOL) False); + ctrl_check_fds(&rfds, &wfds); + if (FD_ISSET(rdp_socket, &rfds)) return 1;