diff --git a/mod_websocket.c b/mod_websocket.c index 45adb8f..1971c2a 100644 --- a/mod_websocket.c +++ b/mod_websocket.c @@ -472,10 +472,31 @@ static void mod_websocket_data_framing(const WebSocketServer *server, { WebSocketState *state = server->state; request_rec *r = state->r; - apr_bucket_brigade *obb = - apr_brigade_create(r->pool, r->connection->bucket_alloc); - if (obb != NULL) { + /* We cannot use the same bucket allocator for the ouput bucket brigade + * obb as the one associated with the connection (r->connection->bucket_alloc) + * because the same bucket allocator cannot be used in two different + * threads, and we use the connection bucket allocator in this + * thread - see docs on apr_bucket_alloc_create(). This results in + * occasional core dumps. So create our own bucket allocator and pool + * for output thread bucket brigade. + */ + + apr_thread_mutex_t *oallocatormutex = NULL; + apr_allocator_t * oallocator = NULL; + apr_pool_t *opool = NULL; + apr_bucket_alloc_t *obucketallocator = NULL; + apr_bucket_brigade * obb = NULL; + + if ( + ( apr_thread_mutex_create(&oallocatormutex, APR_THREAD_MUTEX_UNNESTED, r->pool) == APR_SUCCESS) && + ( apr_allocator_create(&oallocator) == APR_SUCCESS) && + ( apr_allocator_mutex_set(oallocator, oallocatormutex), 1 ) && + ( apr_pool_create_ex(&opool, NULL, NULL, oallocator) == APR_SUCCESS) && /* WARNING: pool has no parent */ + ( NULL != (obucketallocator = apr_bucket_alloc_create(opool))) && + ( NULL != (obb = apr_brigade_create(opool, obucketallocator))) + ) { + unsigned char block[BLOCK_DATA_SIZE]; apr_int64_t block_size; apr_int64_t extension_bytes_remaining = 0; @@ -808,7 +829,15 @@ static void mod_websocket_data_framing(const WebSocketServer *server, /* We are done with the bucket brigade */ apr_thread_mutex_lock(state->mutex); state->obb = NULL; - apr_brigade_destroy(obb); + + /* Destroy the pool (which does not have a parent) manually + * which will destroy (inter alia) the bucket brigade and + * bucket brigade allocator + */ + apr_pool_destroy(opool); + apr_allocator_destroy(oallocator); + /* No need to destroy the mutex as that belongs to r->pool */ + } } @@ -932,7 +961,7 @@ static int mod_websocket_method_handler(request_rec *r) } apr_thread_mutex_create(&state.mutex, - APR_THREAD_MUTEX_DEFAULT, + APR_THREAD_MUTEX_UNNESTED, r->pool); apr_thread_mutex_lock(state.mutex); diff --git a/mod_websocket_draft76.c b/mod_websocket_draft76.c index e62b02c..6b06cb7 100644 --- a/mod_websocket_draft76.c +++ b/mod_websocket_draft76.c @@ -529,13 +529,12 @@ static int mod_websocket_method_handler(request_rec *r) } } - apr_thread_mutex_create(&state.mutex, APR_THREAD_MUTEX_DEFAULT, r->pool); + apr_thread_mutex_create(&state.mutex, APR_THREAD_MUTEX_UNNESTED, r->pool); apr_thread_mutex_lock(state.mutex); /* If the plugin supplies an on_connect function, it must return non-null on success */ if ((conf->plugin->on_connect == NULL) || ((plugin_private = conf->plugin->on_connect(&server)) != NULL)) { - apr_bucket_brigade *obb; /* Now that the connection has been established, disable the socket timeout */ apr_socket_timeout_set(ap_get_module_config(r->connection->conn_config, &core_module), -1); @@ -548,9 +547,21 @@ static int mod_websocket_method_handler(request_rec *r) ap_send_interim_response(r, 1); /* Create the output bucket brigade */ - obb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + apr_thread_mutex_t *oallocatormutex = NULL; + apr_allocator_t * oallocator = NULL; + apr_pool_t *opool = NULL; + apr_bucket_alloc_t *obucketallocator = NULL; + apr_bucket_brigade * obb; + + if ( + ( apr_thread_mutex_create(&oallocatormutex, APR_THREAD_MUTEX_UNNESTED, r->pool) == APR_SUCCESS) && + ( apr_allocator_create(&oallocator) == APR_SUCCESS) && + ( apr_allocator_mutex_set(oallocator, oallocatormutex), 1 ) && + ( apr_pool_create_ex(&opool, NULL, NULL, oallocator) == APR_SUCCESS) && /* WARNING: pool has no parent */ + ( NULL != (obucketallocator = apr_bucket_alloc_create(opool))) && + ( NULL != (obb = apr_brigade_create(opool, obucketallocator))) + ) { - if (obb != NULL) { unsigned char block[BLOCK_DATA_SIZE], *extended_data = NULL; apr_off_t extended_data_offset = 0; apr_size_t block_size, data_length = 0, extended_data_size = 0; @@ -705,7 +716,14 @@ static int mod_websocket_method_handler(request_rec *r) apr_thread_mutex_lock(state.mutex); state.obb = NULL; - apr_brigade_destroy(obb); + /* Destroy the pool (which does not have a parent) manually + * which will destroy (inter alia) the bucket brigade and + * bucket brigade allocator + */ + + apr_pool_destroy(opool); + apr_allocator_destroy(oallocator); + /* No need to destroy the mutex as that belongs to r->pool */ } apr_thread_mutex_unlock(state.mutex); diff --git a/vncproxy/mod_websocket_vnc_proxy.c b/vncproxy/mod_websocket_vnc_proxy.c new file mode 100644 index 0000000..6191853 --- /dev/null +++ b/vncproxy/mod_websocket_vnc_proxy.c @@ -0,0 +1,1587 @@ +/* + * Copyright 2012 Flexiant Ltd + * + * Written by Alex Bligh, based upon the dumb_increment_protocol + * example for apache-websocket, written by self.disconnect + * + * 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. + */ + +/* + * This apache module is a general purpose tcp proxy for apache + * designed to work with libwebsockets. However, it has various + * optimisations for vnc connections. The service to which it connects + * can either be defined in a static manner, or can be looked up in + * a database. The service also supports connecting to an intermediate + * secondary proxy. + * + * Intermediate secondary proxy + * ============================ + * + * The system OPTIONALLY allows for use with an intermediate proxy + * which will forward the onbound connection to its ultimate destination + * This feature is activated by the WebSocketTcpProxySendInitialData directive, + * so called as the outbound session has initial data added which is a cryptographically + * signed instruction to the cluster proxy as to where to forward the + * onbound TCP session. + * + * The data sent consists of an XML object containing: + * - the session key (or a generated one if there is none) + * - a parameter block (if database access is set up) consisting of all the data + * supplied by the database search using the fiels names and values supplied therein + * - a hash of the session key, the parameter block, a nonce supplied by + * the secondary proxy, and a shared secret. + * + * The hash allows the secondary proxy to verify that the incoming connection + * has been supplied by a person in posession of the shared secret. + * + * Database lookups + * ================ + * + * The system OPTIONALLY allows for dyanmic configuration of vnc port forwards looked + * up with an arbitrary key. The key can be composed of any base64 letters plus + * underscores and minus sigs. + * + * The user can specify a statement (likely to be SELECT + * in an SQL environment) which returns data providing the vnc proxy paaramters + * associated with that particular hardware address. + * + * The query is the SELECT statement passed to the SQL backend, into which + * the following are substituted sprintf style paramaters. Currently only + * one parameter is passed, thus use + * %s : the key + * %% : a percent sign + * + * The query does not need a trailing semicolon. Be careful that quotes in the + * query do not interfere with quotes in the config file. + * + * If no rows are returned, the connection will be rejected. If more than one + * row is returned, the first row will be used to connect to. + * + * Columns returned should be + * * the IP address to connect to (connecthost) + * * the port numebr to connect to (connectport) + * * Any other columns you want sent in the initial data + * + * For example, if the table 'vnc' contained columns vncnodehost, vncnodeport + * vncclusterhost, vncclusterport, and vncclusterkey, corresponding to ip and port + * address of the node, the ip and port of the cluster proxy, and the key, + * the following query might be used: + * + * SELECT vncnodehost AS 'nodehost', vncnodeport AS 'nodeport', + * vncclusterhost AS 'connecthost', vncclusterport AS 'connectport' + * FROM vnc WHERE vnckey='%s' + * + * In which case the initial data would include entries for + * nodehost + * nodeport + * host + * port + * + * The nonce sent from the intermediate proxy will be added. + */ + +#include + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "apr_thread_proc.h" +#include "apr_base64.h" +#include "apr_strings.h" +#include "apr_dbd.h" +#include "apr_random.h" +#include "apr_xml.h" +#include "mod_dbd.h" + +#include "websocket_plugin.h" + +#define VNCHEADERMAGIC 0xAB15AB1E +#define VNCGREETINGMAGIC 0x564e4321 + +module AP_MODULE_DECLARE_DATA websocket_vnc_proxy_module; + +typedef struct _vncheader { + uint32_t magic; + uint16_t version; + uint16_t length; +} __attribute__ ((packed)) vncheader; + +typedef struct +{ + char *location; + const char *host; + const char *port; + const char *protocol; + const char *secret; + const char *localip; + int base64; + int sendinitialdata; + int timeout; + int guacamole; + char *query; +} websocket_tcp_proxy_config_rec; + +typedef struct _TcpProxyData +{ + const WebSocketServer *server; + apr_pool_t *pool; + apr_pool_t *threadpool; + apr_allocator_t *threadallocator; + apr_thread_t *thread; + apr_socket_t *tcpsocket; + apr_pollset_t *sendpollset; + int active; + int base64; + int sendinitialdata; + int timeout; + int guacamole; + char *host; + char *port; + char *localip; + char *initialdata; + char *secret; + char *key; + char *nonce; + apr_hash_t * paramhash; + apr_dbd_prepared_t *statement; + websocket_tcp_proxy_config_rec *conf; +} TcpProxyData; + +/* optional functions - look it up once in post_config */ +static ap_dbd_t *(*tcp_proxy_dbd_acquire_fn)(request_rec*) = NULL; +static void (*tcp_proxy_dbd_prepare_fn)(server_rec*, const char*, const char*) = NULL; + +static const char *tcp_proxy_dbd_prepare(cmd_parms *cmd, void *cfg, const char *query) +{ + static unsigned int label_num = 0; + char *label; + + if (tcp_proxy_dbd_prepare_fn == NULL) { + tcp_proxy_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); + if (tcp_proxy_dbd_prepare_fn == NULL) { + return "You must load mod_dbd to enable DBD functions"; + } + tcp_proxy_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); + } + label = apr_psprintf(cmd->pool, "tcp_proxy_dbd_%d", ++label_num); + + tcp_proxy_dbd_prepare_fn(cmd->server, query, label); + + /* save the label here for our own use */ + return ap_set_string_slot(cmd, cfg, label); +} + + +static apr_status_t tcp_proxy_query_key (request_rec * r, TcpProxyData * tpd, apr_pool_t * mp) +{ + /* Check we have a config and a datbase connection */ + + apr_status_t rv; + const char *dbd_password = NULL; + apr_dbd_prepared_t *statement = NULL; + apr_dbd_results_t *res = NULL; + apr_dbd_row_t *row = NULL; + char *c; + + if (!tpd || !tpd->conf) + return (APR_BADARG); + + websocket_tcp_proxy_config_rec *conf = tpd->conf; + + /* If no query is specified, we are fine */ + if (!conf->query) + return APR_SUCCESS; + + /* Check we have a real key */ + if (!tpd->key || !*tpd->key) + return APR_BADARG; + + /* Check the key is valid */ + for (c = tpd->key; *c; c++) { + if (!isalnum(*c)) + switch (*c) { + case ',': + case '-': + case '+': + case '=': + case '/': + case '_': + break; + default: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "DBI: bad key"); + return APR_BADARG; + } + } + + ap_dbd_t *dbd = tcp_proxy_dbd_acquire_fn(r); + if (!dbd) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Failed to acquire database connection to look up " + "key '%s'", tpd->key); + return APR_BADARG; + } + + statement = apr_hash_get(dbd->prepared, conf->query, APR_HASH_KEY_STRING); + if (!statement) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "A prepared statement could not be found for " + "AuthDBDUserPWQuery with the key '%s'", conf->query); + return APR_BADARG; + } + + if (apr_dbd_pvselect(dbd->driver, mp, dbd->handle, &res, statement, + 0, tpd->key) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Query execution error looking up '%s' " + "in database", tpd->key); + return APR_BADARG; + } + + int found = 0; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_query_key: running through results"); + + for (rv = apr_dbd_get_row(dbd->driver, mp, res, &row, -1); + rv != -1; + rv = apr_dbd_get_row(dbd->driver, mp, res, &row, -1)) { + if (rv != 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "Error retrieving results while looking up '%s' " + "in database", tpd->key); + return APR_BADARG; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_query_key: found a matching line"); + + if (!found) { + char *host = NULL; + char *port = NULL; + const char *fieldname; + int i = 0; + + if (NULL != (tpd->paramhash = apr_hash_make(mp))) { + + for (fieldname = apr_dbd_get_name(dbd->driver, res, i); + fieldname != NULL; + fieldname = apr_dbd_get_name(dbd->driver, res, i)) { + + const char *fieldvalue = apr_dbd_get_entry(dbd->driver, row, i++); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_query_key: found field '%s'='%s'", + fieldname, + fieldvalue); + + if (fieldvalue) { + apr_hash_set(tpd->paramhash, apr_pstrdup(mp, fieldname), APR_HASH_KEY_STRING, apr_pstrdup(mp, fieldvalue)); + if (!strcmp(fieldname, "connecthost")) + host = apr_pstrdup(mp, fieldvalue); + else if (!strcmp(fieldname, "connectport")) + port = apr_pstrdup(mp, fieldvalue); + } + } + } + if (tpd->paramhash && host && port) { + tpd->host = host; + tpd->port = port; + found = 1; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_query_key: found parm host=%s port=%s", + tpd->host?tpd->host:"(none)", + tpd->port?tpd->port:"(none)"); + /* we can't break out here or row won't get cleaned up */ + } + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_query_key: found=%d", found); + + if (!found) + return APR_BADARG; + + return APR_SUCCESS; +} + + +/** + * return the 'key=XXX' parameter + */ + +static char *tcp_proxy_get_key(request_rec * r, + TcpProxyData * tpd, apr_pool_t * mp) +{ + const char *args = r->args; + const char *param; + + if (!args) + return NULL; + + while (*args) { + /* get the next parameter */ + param = ap_getword(mp, &args, '&'); + if (!param) + return NULL; + if (!strncmp(param, "key=", 4)) { + return apr_pstrdup(mp, param + 4); + } + } + return NULL; +} + +/** + * Authenticate the connection. This can modify tpd to change (for instance) + * the host or port to connect to, or set up initialdata. For now it is a stub. + */ + +static apr_status_t tcp_proxy_do_authenticate(request_rec * r, + TcpProxyData * tpd, + apr_pool_t * mp) +{ + if (!tpd->conf) + return APR_BADARG; + + tpd->key = tcp_proxy_get_key(r, tpd, mp); + if (!tpd->conf->query && !tpd->key) { + /* key is option if no query */ + tpd->key = apr_pstrdup(mp, ""); + } + if (!tpd->key) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_authenticate: no key"); + return APR_BADARG; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_authenticate: key is '%s'", + tpd->key); + + /* Look up tpd->host, tpd->port, and other parameters using key */ + if (APR_SUCCESS != tcp_proxy_query_key(r, tpd, mp)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_authenticate: query_key failed"); + return APR_BADARG; + } + + if (!(tpd->host && tpd->port)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_authenticate: missing parm host=%s port=%s", + tpd->host?tpd->host:"(none)", + tpd->port?tpd->port:"(none)" + ); + return APR_BADARG; + } + + return APR_SUCCESS; +} + +/** + * Send the initial data - this would normally be generated by tcp_proxy_do_authenticate + */ + +static apr_status_t tcp_proxy_send_initial_data(request_rec * r, + TcpProxyData * tpd, + apr_pool_t * mp) +{ + vncheader header; + apr_status_t rv; + apr_size_t hlen = sizeof (vncheader); + apr_size_t len; + + if (!tpd->sendinitialdata) + return APR_SUCCESS; + + rv = apr_socket_recv(tpd->tcpsocket, (void *)&header, &hlen); + if (rv != APR_SUCCESS) + return rv; + + if (hlen != sizeof (vncheader)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: could not read whole header"); + return APR_BADARG; + } + + if (ntohl (header.magic) != VNCGREETINGMAGIC) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: bad magic"); + return APR_BADARG; + } + + if (ntohs (header.version) != 1) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: bad version"); + return APR_BADARG; + } + + len = ntohs (header.length); + + if (len>1024) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: bad length"); + return APR_BADARG; + } + + if (NULL == (tpd->nonce = apr_palloc(mp, len+1))) + return APR_BADARG; + + tpd->nonce[len] = 0; /* zero terminate */ + + rv = apr_socket_recv(tpd->tcpsocket, (void *)tpd->nonce, &len); + if (rv != APR_SUCCESS) + return rv; + + if (len != ntohs (header.length)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: could not read whole header (2)"); + return APR_BADARG; + } + + /* ignore /r /n and anything after whitespace */ + char *p; + for (p=tpd->nonce; *p; p++) { + if (isspace(*p)) { + *p=0; + break; + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: read nonce of '%s'", tpd->nonce); + + if (!(tpd->key && tpd->host && tpd->port && tpd->nonce)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_send_initial_data: missing parm key=%s host=%s port=%s nonce=%s", + tpd->key?tpd->key:"(none)", + tpd->host?tpd->host:"(none)", + tpd->port?tpd->port:"(none)", + tpd->nonce?tpd->nonce:"(none)" + ); + return APR_BADARG; + } + + char *tohash = + apr_psprintf(mp, "%s %s %s", tpd->key, tpd->secret, tpd->nonce); + + char *params = apr_pstrdup(mp, ""); + + if (tpd->paramhash) { + apr_hash_index_t *hi; + for (hi = apr_hash_first(mp, tpd->paramhash); hi; hi = apr_hash_next(hi)) { + char * key = NULL; + char * value = NULL;; + apr_hash_this(hi, (const void **)&key, NULL, (void **)&value); + if (key && value) { + tohash = apr_psprintf(mp, "%s %s %s", tohash, key, value); + const char * quotedstring = apr_xml_quote_string(mp, value, 0); + params = apr_psprintf(mp, "%s<%s>%s", params, key, quotedstring, key); + } + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_send_initial_data: Data to hash is '%s'", tohash); + + char hashdata[32]; + apr_crypto_hash_t *h = apr_crypto_sha256_new(mp); + h->init(h); + h->add(h, tohash, strlen(tohash)); + h->finish(h, hashdata); + char hash[32*2+1]; + int i; + for (i=0; i<32; i++) { + sprintf(hash+i*2, "%02hhx", hashdata[i]); + } + hash[32*2]=0; + + tpd->initialdata = apr_psprintf(mp, "" + "%s%s%s" + "", + tpd->key, hash, params); + + if (!tpd->initialdata) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "tcp_proxy_send_initial_data: could not generate initial data"); + return APR_BADARG; + } + + len = strlen(tpd->initialdata); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_send_initial_data: initial data is '%s'", + tpd->initialdata); + + header.magic = htonl(VNCHEADERMAGIC); + header.version = htons(1); + header.length = htons(len); + hlen = sizeof (vncheader); + + rv = apr_socket_send(tpd->tcpsocket, (void *)&header, &hlen); + if (rv != APR_SUCCESS) + return rv; + + return apr_socket_send(tpd->tcpsocket, tpd->initialdata, &len); +} + +/** + * Shutdown the tcpsocket which will cause further read/writes + * in either direction to fail + */ + +static void tcp_proxy_shutdown_socket(TcpProxyData * tpd) +{ + if (tpd && tpd->tcpsocket) + apr_socket_shutdown(tpd->tcpsocket, APR_SHUTDOWN_READWRITE); +} + +/** + * Connect to the remote host + */ +static apr_status_t tcp_proxy_do_tcp_connect(request_rec * r, + TcpProxyData * tpd, + apr_pool_t * mp) +{ + apr_sockaddr_t *sa; + apr_socket_t *s; + apr_status_t rv; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_tcp_connect: connect to host %s port %s", + tpd->host, tpd->port); + + int port = atoi(tpd->port); + rv = apr_sockaddr_info_get(&sa, tpd->host, APR_INET, port, 0, mp); + if (rv != APR_SUCCESS) { + return rv; + } + + if (!port) { + rv = apr_getservbyname(sa, tpd->port); + if (rv != APR_SUCCESS) { + return rv; + } + } + + rv = apr_socket_create(&s, sa->family, SOCK_STREAM, APR_PROTO_TCP, mp); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_interval_time_t timeout = APR_USEC_PER_SEC * ((tpd->timeout)?tpd->timeout:30); + + if (tpd->localip) { + apr_sockaddr_t *localsa; + rv = apr_sockaddr_info_get(&localsa, tpd->localip, APR_UNSPEC, 0 /*port*/, 0, mp); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_tcp_connect: could not get addr to bind to local address %s", + tpd->localip); + apr_socket_close(s); + return rv; + } + if ((rv = apr_socket_bind(s, localsa)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_do_tcp_connect: could not bind to local address %s", + tpd->localip); + apr_socket_close(s); + return rv; + } + } + + /* it is a good idea to specify socket options explicitly. + * in this case, we make a blocking socket with timeout. */ + apr_socket_opt_set(s, APR_SO_NONBLOCK, 0); + apr_socket_opt_set(s, APR_SO_KEEPALIVE, 1); + apr_socket_timeout_set(s, timeout); + + rv = apr_socket_connect(s, sa); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "Cannot connect to host %s port %s", + tpd->host, tpd->port); + apr_socket_close(s); + return rv; + } + + /* Set it to be blocking to start off with */ + apr_socket_opt_set(s, APR_SO_NONBLOCK, 0); + apr_socket_opt_set(s, APR_SO_KEEPALIVE, 1); + apr_socket_timeout_set(s, timeout); + + tpd->tcpsocket = s; + return APR_SUCCESS; +} + + +void guacdump (apr_pool_t * p, char * msg, char * buf, size_t start, size_t end) +{ + size_t s = end-start+1; + char * b = malloc(s); + if (b) { + memcpy(b, buf+start, s-1); + b[s-1]=0; + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, p, "%s: '%s'", msg, b); + free (b); + } +} + +/* This function READS from the tcp socket and WRITES to the web socket */ +/* We will not use ap_log_error in this functin because of potential lack of thread + * safety on the allocator. Instead, we shall use ap_log_perror + */ + +void *APR_THREAD_FUNC tcp_proxy_run(apr_thread_t * thread, void *data) +{ + char buffer[64]; + apr_status_t rv; + TcpProxyData *tpd = (TcpProxyData *) data; + + if (!tpd) + return NULL; + + request_rec *r = (tpd->server)->request(tpd->server); + + apr_interval_time_t timeout = APR_USEC_PER_SEC * ((tpd->timeout)?tpd->timeout:30); + apr_pollset_t * recvpollset = NULL; + + if ((APR_SUCCESS != (rv = apr_pollset_create (&recvpollset, 32, tpd->threadpool, APR_POLLSET_THREADSAFE))) || + !recvpollset) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, rv, tpd->threadpool, "tcp_proxy_run pollset create failed"); + return NULL; + } + + apr_pollfd_t recvpfd = { tpd->threadpool, APR_POLL_SOCKET, APR_POLLIN, 0, { NULL }, NULL }; + recvpfd.desc.s = tpd->tcpsocket; + apr_pollset_add(recvpollset, &recvpfd); + + if (!tpd->guacamole) { + /* Non-guacamole mode - buffer as much as we can */ + + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run start"); + +#define WSTCPBUFSIZ 16384 +#define WSTCPCBUFSIZ ((WSTCPBUFSIZ*4/3)+5) +#define GUARDBYTES 64 + char buf[WSTCPBUFSIZ]; + char cbuf[WSTCPCBUFSIZ]; + apr_size_t got=0; + + /* Keep sending messages as long as the connection is active */ + while (tpd->active && tpd->tcpsocket) { + + /* we can read an entire buffer length, less what we have got so far */ + apr_size_t len = sizeof(buf) - got; + + const apr_pollfd_t *ret_pfd = NULL; + apr_int32_t num = 0; + + rv = apr_pollset_poll(recvpollset, got?1000:timeout, &num, &ret_pfd); + + if (!(tpd->active && tpd->tcpsocket)) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run quitting as connection has been marked inactive"); + break; + } + + if (num<=0) { + /* We've got nothing to do */ + if (APR_STATUS_IS_TIMEUP(rv)) { + len=0; + goto disgorgeandcontinue; + } + + if (rv == APR_SUCCESS) { + /* Poll returned success, but no descriptors were ready. Very odd */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run: sleeping 2"); + usleep(10000); /* this should not happen */ + } + + ap_log_perror(APLOG_MARK, APLOG_DEBUG, rv, tpd->threadpool, "tcp_proxy_run: poll returned an error"); + break; + } + + rv = apr_socket_recv(tpd->tcpsocket, buf+got, &len); + + /* recv can return data *AND* an error - deal with data first*/ + got+=len; + + disgorgeandcontinue: + /* if the buffer is more than half full, or we had nothing to read */ + if ((got > WSTCPBUFSIZ/2) || (num<=0)) { + + size_t towrite = got; + + char *wbuf = buf; + + /* Base64 encode it if necessary */ + if (tpd->base64) { + towrite = apr_base64_encode(cbuf, buf, towrite); + wbuf = cbuf; + } + + size_t written = + tpd->server->send(tpd->server, MESSAGE_TYPE_TEXT /* FIXME */ , + (unsigned char *) wbuf, towrite); + if (written != towrite) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run send failed, wrote %lu bytes of %lu", + (unsigned long) written, (unsigned long) got); + break; + } + got=0; + } + + if (APR_STATUS_IS_TIMEUP(rv)) + continue; + + if (rv == APR_SUCCESS) { + if (!len) { + /* Hmm, we got success, or timeup in which case we want to loop + * but we might get no data again, so we wait just in case - there seem + * to be conditions where this happens in a circumstance where a repeat + * read produces the same error, so sleep so we don't busy-wait CPU + */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run: sleeping"); + usleep(10000); /* this should not happen */ + } + continue; + } + + char s[1024]; + apr_strerror(rv, s, sizeof(s)); + ap_log_perror(APLOG_MARK, APLOG_DEBUG, rv, tpd->threadpool, + "tcp_proxy_run apr_socket_recv failed len=%lu rv=%d, %s", + (unsigned long) len, rv, s); + + break; + } + + tcp_proxy_shutdown_socket(tpd); + tpd->server->close(tpd->server); + + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run stop"); + + } else { + + /* We're in guacamole mode. Guacamole (unfortunately) requires that its messages + * are not broken across websocket frames. This means we need to understand the + * underlying protocol as we have no idea what tcp buffering might have done on + * the way. + * + * For now we will use one websocket message per guacamole instruction. + * + * Guacamole protocol is described at + * http://guac-dev.org/Guacamole%20Protocol + * + * In essence it is a text base protocol made up of instructions. Each instruction is + * a comma delimited list followed by a terminating semicolon. This semicolon is + * immediately followed by the next instruction. Each instruction takes the form + * OPCODE,ARG1,ARG2,...; + * Each OPCADE and ARG can contain any character (including a semicolon) so we can't + * just look for semicolos. But fortunately each OPCODE or ARG takes the form + * LENGTH.VALUE + * where LENGTH is a decimal integer length of the VALUE field (excluding the + * dot). The VALUE field is not null terminated. So, for instance: + * 4.size,1.0,4.1024,3.768; + * + * We don't use apache memory handling here because of the lack of realloc and/or + * explicit free. + */ + + /* + * Buffer arrangement + * + * 0 bufwritep bufreadp bufsize + * V V V V + * XXXXXXXXXXXXXXDDDDDDDDDDDDDDDDDD------------| + * | | | | + * | | | \_ Free memory + * | \ \ + * | \ \_____ Data yet to be written to websocket + * | \ + * buf \______ Data already written to websocket + */ + + size_t bufsize = 0; + size_t bufwritep = 0; + size_t bufreadp = 0; + const size_t minread = 1024; + const size_t maxbufsize = 16*1024*1024; + char * buf = NULL; + + + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run start guacamole mode"); + + /* Keep sending messages as long as the connection is active */ + while (tpd->active && tpd->tcpsocket) { + + if ((bufreadp > bufsize) || (bufwritep > bufreadp)) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run guacamole pointer error, buf=%lx bufsize=%lu bufreadp=%lu bufwritep=%lu", (intptr_t)buf, bufsize, bufreadp, bufwritep); + goto guacerror; + } + + /* First let's see if we've got a completely empty buffer */ + if (bufreadp == bufwritep) { + /* If so, junk all the data written to the websocket without + * reallocating the buffer */ + bufreadp = 0; + bufwritep = 0; + if (bufsize > minread) { + /* The buffer was grown, and now is empty, so we might as well free it + * up to free memory, which means it will be reallocated down below + */ + free(buf); + buf = NULL; + bufsize=0; + } + } + + /* We know we need to read at least minread bytes + * so the easy case is that they just fit in the current buffer + */ + if (bufsize-bufreadp < minread) { + /* Right, we can't fit it in the current buffer. Where + * bufindex > 0 we've got current data, so we'll + * reallocate and expunge that first + */ + if (bufwritep > 0) { + char * newbuf = malloc(bufsize + GUARDBYTES); + if (!newbuf) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run could not allocate guacamole buffer"); + goto guacerror; + } + if (buf && (bufreadp > bufwritep)) + memcpy(newbuf, buf+bufwritep, bufreadp-bufwritep); + bufreadp -= bufwritep; + bufwritep = 0; + if (buf) + free (buf); + buf = newbuf; + } + + /* We now know bufwritep is zero, i.e. there is no data that has + * already been written hanging around. So lets see whether we + * can do a read of length minread now + */ + if (bufsize-bufreadp < minread) { + /* No we can't, so we straightforwardly need a larger buffer. + * (a buffer might not have been allocated yet) + */ + size_t newbufsize = bufsize * 2; /* make sure we double the size of the buffer */ + if (newbufsize > maxbufsize) + newbufsize = maxbufsize; /* but don't make it larger than the maximum */ + if (newbufsize < bufreadp + minread) /* Make it large enough for the read we need */ + newbufsize = bufreadp + minread; /* Note this is how the initial size is set */ + if ((newbufsize > maxbufsize) || (newbufsize < bufsize)) + { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run guacamole buffer grew to illegal size"); + goto guacerror; + } + /* + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run expanding guacamole buffer to %lu bytes", newbufsize); + */ + char * newbuf = realloc (buf, newbufsize + GUARDBYTES); /* realloc when buf in NULL is a malloc */ + if (!newbuf) { + /* remember to free buf */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run could not reallocate guacamole buffer"); + goto guacerror; + } + buf = newbuf; + bufsize = newbufsize; + } + } + + /* Check we now have a buffer and sace to read into - this should always be the case */ + if (!buf || (bufsize-bufreadp < minread)) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run guacamole logic error, buf=%lx bufsize=%lu bufread=%lu minread=%lu", (intptr_t)buf, bufsize, bufreadp, minread); + goto guacerror; + } + + apr_size_t len; + + while (1) { + /* Of course we may be able to read far more than minread, so let's go for that */ + len = bufsize - bufreadp; + + const apr_pollfd_t *ret_pfd = NULL; + apr_int32_t num = 0; + + rv = apr_pollset_poll(recvpollset, timeout, &num, &ret_pfd); + + if (!(tpd->active && tpd->tcpsocket)) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run quitting guacamole mode as connection has been marked inactive"); + goto guacdone; + } + + if (APR_STATUS_IS_TIMEUP(rv)) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run quitting guacamole mode as ws poll has timed out"); + goto guacdone; + } + + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, rv, tpd->threadpool, "tcp_proxy_run: poll returned an error"); + goto guacerror; + } + + if (num<=0) { + /* Poll returned success, but no descriptors were ready. Very odd */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run: sleeping guac 2"); + usleep(10000); /* this should not happen */ + continue; + } + + rv = apr_socket_recv(tpd->tcpsocket, buf+bufreadp, &len); + if (APR_STATUS_IS_EAGAIN(rv)) { /* we have no data to read yet, we should try rereading */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run: sleeping guac 3"); + usleep(10000); + continue; + } + + if (APR_STATUS_IS_EOF(rv) || !len) { + /* we lost the TCP session */ + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run quitting guacamole mode as TCP connection closed"); + goto guacdone; + } + + /* We have data */ + break; + } + + bufreadp += len; + + /* + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run ***guac read bytes len=%lu bufwrirep=%lu bufreadpp=%lu", len, bufreadp, bufwritep); + */ + + /* So now we have an instruction starting at bufwritep, and terminating either before + * bufreadp (in which case we can write it and look for more) or possibly not terminating + * in which case we need to loop around again to read more data + */ + + size_t p = bufwritep; + size_t lastwholecommand = bufwritep; + size_t towrite = 0; + while (p < bufreadp) { + + /* Skip along until we find a semicolon */ + int write=0; + while (!write) { + /* + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run ***guac decode loop p=%lu bufwrirep=%lu bufreadpp=%lu", p, bufreadp, bufwritep); + guacdump(tpd->threadpool, "tcp_proxy_run ***guac string is", buf, p, bufreadp); + */ + + if (p >= bufreadp) + goto writelastwholecommand; + size_t arglen = 0; + while (isdigit(buf[p])) { + arglen = arglen * 10 + ( buf[p++] - '0'); + if (p >= bufreadp) + goto writelastwholecommand; + } + /* arglen must be non-zero, and we know buf[p] is valid (as pthreadpool, + "tcp_proxy_run bad guacamole length"); + goto guacerror; + } + /* So, consider, to step to the comma we need to add arglen+1 + * 4.size, + * ^ + * p + */ + p+=arglen+1; + if (p >= bufreadp) + goto writelastwholecommand; + switch (buf[p++]) { + case ',': + continue; + case ';': + write = 1; + break; + default: + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run bad guacamole terminator"); + goto guacerror; + break; + } + } + + lastwholecommand = p; + + /* And loop to see whether we have any more instructions */ + } + + writelastwholecommand: + /* So now we know we can write bufwritep ... lastwholecommand */ + + /* FIXME: support base64 - actually guacamole doesn't use it */ + + towrite = lastwholecommand - bufwritep; + + if (towrite > 0) { + size_t written = + tpd->server->send(tpd->server, MESSAGE_TYPE_TEXT, + (unsigned char *) (buf + bufwritep), towrite); + if (written != towrite) { + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, + "tcp_proxy_run guacamole send failed, wrote %lu bytes of %lu", + (unsigned long) written, (unsigned long) len); + goto guacerror; + } + + /* Step forward past the bit we've just written */ + bufwritep = lastwholecommand; + } + + } + + guacdone: + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, tpd->threadpool, "tcp_proxy_run stop guacamole mode"); + guacerror: + if (buf) + free (buf); + tcp_proxy_shutdown_socket(tpd); + tpd->server->close(tpd->server); + return NULL; + } + + return NULL; + +} + +/* this routine takes data FROM the web socket and writes it to the tcp socket */ + +static size_t CALLBACK tcp_proxy_on_message(void *plugin_private, + const WebSocketServer * server, + const int type, + unsigned char *buffer, + const size_t buffer_size) +{ + TcpProxyData *tpd = (TcpProxyData *) plugin_private; + + request_rec *r = server->request(server); + + if (tpd && tpd->tcpsocket) { + apr_size_t len = buffer_size; + apr_status_t rv; + unsigned char *towrite = buffer; + + if (len<=0) + return 0; + + if (tpd->base64) { + /* Unfortunately we cannot guarantee our buffer is 0 terminated, which irritatingly + * means we have to copy it + */ + towrite = NULL; + unsigned char *ztbuf = calloc(1, len + 1); + if (!ztbuf) + goto fail; + towrite = calloc(1, len + 1); + if (!towrite) { + free(ztbuf); + goto fail; + } + memcpy(ztbuf, buffer, len); + len = apr_base64_decode_binary(towrite, ztbuf); + free(ztbuf); + if (len <= 0) { + free(towrite); + towrite = NULL; + } + fail: + if (!towrite) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_message: apr_base64_decode_binary failed"); + tcp_proxy_shutdown_socket(tpd); + tpd->server->close(tpd->server); + return 0; + } + } + + apr_interval_time_t timeout = APR_USEC_PER_SEC * ((tpd->timeout)?tpd->timeout:30); + rv = APR_SUCCESS; + unsigned char * p = towrite; + apr_size_t l = len; + + while (l>0) { + + if (!(tpd->active && tpd->tcpsocket)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_message quitting as connection has been marked inactive"); + rv = APR_BADARG; + break; + } + + const apr_pollfd_t *ret_pfd = NULL; + apr_int32_t num = 0; + + rv = apr_pollset_poll(tpd->sendpollset, timeout, &num, &ret_pfd); + + if (num>0) { + apr_size_t lw = l; + rv = apr_socket_send(tpd->tcpsocket, p, &lw); + + /* move past data written */ + l -= lw; + p += lw; + + if (APR_STATUS_IS_TIMEUP(rv)) + continue; + + if (rv == APR_SUCCESS) { + if (!lw) { + /* check for success, but successfully wrote nothing */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_message: sleeping"); + usleep(10000); /* this should not happen */ + } + continue; + } + /* so the send errored, break with rv set correctly */ + break; + } + + /* + * Here we're checking rv from poll + */ + if (APR_STATUS_IS_TIMEUP(rv)) + continue; + + if (rv == APR_SUCCESS) { + /* Hmmm... we polled, it said success (not timeout) but nothing was + * ready + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_message: sleeping 2"); + usleep(10000); /* this should not happen */ + continue; + } + + /* OK, poll errored in a peculiar way */ + break; + } + + if (tpd->base64) + free(towrite); + + if (rv != APR_SUCCESS) { + char s[1024]; + apr_strerror(rv, s, sizeof(s)); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_message: apr_socket_send failed, rv=%d, sent=%lu, %s", + rv, (unsigned long) len, s); + tcp_proxy_shutdown_socket(tpd); + tpd->server->close(tpd->server); + return 0; + } + } + + return 0; +} + +void *CALLBACK tcp_proxy_on_connect(const WebSocketServer * server) +{ + TcpProxyData *tpd = NULL; + + /* Get access to the request_rec strucure for this connection */ + request_rec *r = server->request(server); + if (!r) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_connect bad request"); + return NULL; + } + + if (!server || (server->version != WEBSOCKET_SERVER_VERSION_1)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_connect bad server"); + return NULL; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_connect starting"); + + size_t i = 0, count = server->protocol_count(server); + + websocket_tcp_proxy_config_rec *conf = + (websocket_tcp_proxy_config_rec *) + ap_get_module_config(r->per_dir_config, + &websocket_vnc_proxy_module); + const char *requiredprotocol = conf ? conf->protocol : NULL; + + if (requiredprotocol) { + for (i = 0; i < count; i++) { + const char *protocol = server->protocol_index(server, i); + + if (protocol && (strcmp(protocol, requiredprotocol) == 0)) { + /* If the client can speak the protocol, set it in the response */ + server->protocol_set(server, protocol); + break; + } + } + } + else { + count = 1; /* ensure i=count) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_connect bad protocol"); + return NULL; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_connect protocol correct"); + + /* We create two pools. 'pool' is for access by this thread, 'threadpool' is for + * access by thre thread, and has a separate allocator, no parent, and is freed + * manually. + */ + apr_pool_t *pool = NULL; + apr_pool_t *threadpool = NULL; + apr_thread_mutex_t * threadallocatormutex = NULL; + apr_allocator_t * threadallocator = NULL; + + if (!( ( apr_pool_create(&pool, r->pool) == APR_SUCCESS) && + ( apr_thread_mutex_create(&threadallocatormutex, APR_THREAD_MUTEX_UNNESTED, pool) == APR_SUCCESS) && + ( apr_allocator_create(&threadallocator) == APR_SUCCESS) && + ( apr_allocator_mutex_set(threadallocator, threadallocatormutex), 1 ) && + ( apr_pool_create_ex(&threadpool, NULL, NULL, threadallocator) == APR_SUCCESS) && /* WARNING: pool has no parent */ + threadpool && threadallocator && threadallocatormutex && pool + )) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_connect could not allocate pool"); + return NULL; + } + + /* Past this point we must ensure the allocator and the pool are manually destroyed */ + + /* Allocate memory to hold the tcp proxy state */ + if (NULL == (tpd = (TcpProxyData *) apr_palloc(pool, sizeof(TcpProxyData)))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_connect could not allocate tpd structure"); + goto destroypool; + } + + apr_thread_t *thread = NULL; + apr_threadattr_t *thread_attr = NULL; + + tpd->server = server; + tpd->pool = pool; + tpd->thread = NULL; + tpd->tcpsocket = NULL; + tpd->active = 1; + tpd->base64 = 0; + tpd->sendinitialdata = 0; + tpd->timeout = 30; + tpd->guacamole = 0; + tpd->port = "echo"; + tpd->host = "127.0.0.1"; + tpd->secret = "none"; + tpd->initialdata = NULL; + tpd->nonce = NULL; + tpd->sendpollset = NULL; + tpd->key = NULL; + tpd->conf = conf; + tpd->paramhash = NULL; + tpd->statement = NULL; + tpd->localip = NULL; + + if (!conf) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_connect: no config"); + goto destroypool; + } + + tpd->base64 = conf->base64; + tpd->sendinitialdata = conf->sendinitialdata; + tpd->timeout = conf->timeout; + tpd->guacamole = conf->guacamole; + if (conf->host) + tpd->host = apr_pstrdup(pool, conf->host); + if (conf->port) + tpd->port = apr_pstrdup(pool, conf->port); + if (conf->secret) + tpd->secret = apr_pstrdup(pool, conf->secret); + if (conf->localip) + tpd->localip = apr_pstrdup(pool, conf->localip); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_connect: base64 is %d", + conf->base64); + + /* Check we can authenticate the incoming user (this is a hook for others to add to) + * Check we can connect + * And if we have initial data to send, then send that + */ + if (!((APR_SUCCESS == tcp_proxy_do_authenticate(r, tpd, pool)) && + (APR_SUCCESS == tcp_proxy_do_tcp_connect(r, tpd, pool)) && + (APR_SUCCESS == tcp_proxy_send_initial_data(r, tpd, pool)) + )) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "tcp_proxy_on_connect: closing connection as authentication / initial data failed"); + goto destroypool; + } + + /* see the tutorial about the reason why we have to specify options again */ + apr_socket_opt_set(tpd->tcpsocket, APR_SO_NONBLOCK, 1); + apr_socket_opt_set(tpd->tcpsocket, APR_SO_KEEPALIVE, 1); + apr_socket_timeout_set(tpd->tcpsocket, 0); + + apr_pollset_create(&tpd->sendpollset, 32, pool, APR_POLLSET_THREADSAFE); + apr_pollfd_t sendpfd = { pool, APR_POLL_SOCKET, APR_POLLOUT, 0, { NULL }, NULL }; + sendpfd.desc.s = tpd->tcpsocket; + apr_pollset_add(tpd->sendpollset, &sendpfd); + + tpd->threadpool = threadpool; + tpd->threadallocator = threadallocator; + + /* Create a non-detached thread that will perform the work */ + if ((APR_SUCCESS == apr_threadattr_create(&thread_attr, pool)) && + (APR_SUCCESS == apr_threadattr_detach_set(thread_attr, 0)) && + (APR_SUCCESS == apr_thread_create(&thread, thread_attr, tcp_proxy_run, tpd, pool)) + ) { + tpd->thread = thread; + /* Success */ + return tpd; + } + tpd->threadpool = NULL; + tpd->threadallocator = NULL; + + destroypool: + apr_pool_destroy(threadpool); + apr_allocator_destroy(threadallocator); + return NULL; +} + +void CALLBACK tcp_proxy_on_disconnect(void *plugin_private, + const WebSocketServer * server) +{ + TcpProxyData *tpd = (TcpProxyData *) plugin_private; + + request_rec *r = server->request(server); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tcp_proxy_on_disconnect"); + + if (tpd) { + /* When disconnecting, inform the thread that it is time to stop */ + tpd->active = 0; + tcp_proxy_shutdown_socket(tpd); + if (tpd->thread) { + apr_status_t status; + + /* Wait for the thread to finish */ + status = apr_thread_join(&status, tpd->thread); + } + if (tpd->threadpool) { + apr_pool_destroy(tpd->threadpool); + tpd->threadpool = NULL; + } + if (tpd->threadallocator) { + apr_allocator_destroy(tpd->threadallocator); + tpd->threadallocator = NULL; + } + tcp_proxy_shutdown_socket(tpd); + + if (tpd->tcpsocket) { + apr_socket_close(tpd->tcpsocket); + tpd->tcpsocket = NULL; + } + } +} + +/* + * Since we are returning a pointer to static memory, there is no need for a + * "destroy" function. + */ + +static WebSocketPlugin s_plugin = { + sizeof(WebSocketPlugin), + WEBSOCKET_PLUGIN_VERSION_0, + NULL, /* destroy */ + tcp_proxy_on_connect, + tcp_proxy_on_message, + tcp_proxy_on_disconnect +}; + +extern EXPORT WebSocketPlugin *CALLBACK vnc_proxy_init() +{ + return &s_plugin; +} + +static const char *mod_websocket_tcp_proxy_conf_base64(cmd_parms * cmd, + void *config, int flag) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->base64 = flag; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_guacamole(cmd_parms * cmd, + void *config, int flag) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->guacamole = flag; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_sendinitialdata(cmd_parms * cmd, + void *config, int flag) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->sendinitialdata = flag; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_host(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->host = arg; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_port(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->port = arg; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_protocol(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->protocol = strcmp(arg, "any") ? arg : NULL; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_timeout(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->timeout = atoi(arg); + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_secret(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->secret = arg; + return NULL; +} + +static const char *mod_websocket_tcp_proxy_conf_localip(cmd_parms * cmd, + void *config, + const char *arg) +{ + websocket_tcp_proxy_config_rec *cfg = + (websocket_tcp_proxy_config_rec *) config; + cfg->localip = arg; + return NULL; +} + +static const command_rec mod_websocket_tcp_proxy_cmds[] = { + AP_INIT_FLAG("WebSocketTcpProxyBase64", + mod_websocket_tcp_proxy_conf_base64, NULL, OR_AUTHCFG, + "Flag to indicate use of base64 encoding; defaults to off"), + AP_INIT_FLAG("WebSocketTcpProxyGuacamole", + mod_websocket_tcp_proxy_conf_guacamole, NULL, OR_AUTHCFG, + "Flag to indicate use of guacamole protocol; defaults to off"), + AP_INIT_FLAG("WebSocketTcpProxySendInitialData", + mod_websocket_tcp_proxy_conf_sendinitialdata, NULL, OR_AUTHCFG, + "Flag to indicate need to send initial data; defaults to off"), + AP_INIT_TAKE1("WebSocketTcpProxyHost", mod_websocket_tcp_proxy_conf_host, + NULL, OR_AUTHCFG, + "Host to connect WebSockets TCP proxy to; default 127.0.0.1"), + AP_INIT_TAKE1("WebSocketTcpProxyPort", mod_websocket_tcp_proxy_conf_port, + NULL, OR_AUTHCFG, + "Port to connect WebSockets TCP proxy to; default echo"), + AP_INIT_TAKE1("WebSocketTcpProxyProtocol", + mod_websocket_tcp_proxy_conf_protocol, NULL, OR_AUTHCFG, + "WebSockets protocols to accept, or 'any'; default 'any'"), + AP_INIT_TAKE1("WebSocketTcpProxyTimeout", + mod_websocket_tcp_proxy_conf_timeout, NULL, OR_AUTHCFG, + "WebSockets proxy connection timeout in seconds; default 30"), + AP_INIT_TAKE1("WebSocketTcpProxySecret", + mod_websocket_tcp_proxy_conf_secret, + NULL, OR_AUTHCFG, + "WebSockets connection secret; default none"), + AP_INIT_TAKE1("WebSocketTcpProxyLocalIP", + mod_websocket_tcp_proxy_conf_localip, + NULL, OR_AUTHCFG, + "WebSockets connection local IP for outbound connections; default unset"), + AP_INIT_TAKE1("WebSocketTcpProxyQuery", tcp_proxy_dbd_prepare, + (void *)APR_OFFSETOF(websocket_tcp_proxy_config_rec, query), OR_AUTHCFG, + "Query used to fetch password for user"), + {NULL} +}; + +static void *mod_websocket_tcp_proxy_create_dir_config(apr_pool_t * p, + char *path) +{ + websocket_tcp_proxy_config_rec *conf = NULL; + + if (path != NULL) { + conf = apr_pcalloc(p, sizeof(websocket_tcp_proxy_config_rec)); + if (conf != NULL) { + conf->location = apr_pstrdup(p, path); + conf->base64 = 0; + conf->sendinitialdata = 0; + conf->guacamole = 0; + conf->host = apr_pstrdup(p, "127.0.0.1"); + conf->port = apr_pstrdup(p, "echo"); + conf->secret = apr_pstrdup(p, "none"); + conf->localip = NULL; + conf->protocol = NULL; + conf->timeout = 30; + conf->query = NULL; + } + } + return (void *) conf; +} + +static int mod_websocket_tcp_proxy_method_handler(request_rec * r) +{ + return DECLINED; +} + +static void mod_websocket_tcp_proxy_register_hooks(apr_pool_t * p) +{ + ap_hook_handler(mod_websocket_tcp_proxy_method_handler, NULL, NULL, + APR_HOOK_LAST); +} + +module AP_MODULE_DECLARE_DATA websocket_vnc_proxy_module = { + STANDARD20_MODULE_STUFF, + mod_websocket_tcp_proxy_create_dir_config, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create server config structure */ + NULL, /* merge server config structures */ + mod_websocket_tcp_proxy_cmds, /* command table */ + mod_websocket_tcp_proxy_register_hooks, /* hooks */ +}; diff --git a/vncproxy/test/Makefile b/vncproxy/test/Makefile new file mode 100644 index 0000000..6cf9d54 --- /dev/null +++ b/vncproxy/test/Makefile @@ -0,0 +1,52 @@ +OUTPUT = ../build/guactest +TARGET = guactest + +# Specify the list of objects, relative to the OUTPUT directory +OBJS = guactest.o utils.o +#DEBUG = -g -DDEBUG_COND_WAIT +#DEBUG = -g -DDEBUG_RBTREES +DEBUG = -g + +CFLAGS = -Wall -c $(DEBUG) -I. -lpthread +LFLAGS = -Wall $(DEBUG) -lpthread +CC = gcc + +IMGS = daisy-glass-art.png dandelions-2.png red-tulips-flower.png single-pixel.png +IMGH = $(OUTPUT)/image.h + +# Work out the build directory variants +BUILDOBJECTS := $(patsubst %o, $(OUTPUT)/%o, $(OBJS)) + +# Get the list of output directories +BUILDDIRS := $(sort $(foreach dir,$(BUILDOBJECTS),$(shell dirname $(dir)))) + +# First target for bare "make" +all: $(OUTPUT)/$(TARGET) + +clean: + @/bin/rm -rf $(OUTPUT)/* + +$(OUTPUT)/$(TARGET): $(BUILDOBJECTS) + @mkdir -p $(OUTPUT) + $(CC) ${BUILDOBJECTS} ${LFLAGS} -o $(OUTPUT)/$(TARGET) + +# automatic dependencies - pull in dependency info for *existing* .o files +-include $(BUILDOBJECTS:.o=.d) + +# autogenerate dependencies after a successful compilation +$(OUTPUT)/%.o: %.c $(IMGH) + @mkdir -p $(BUILDDIRS) + $(CC) -c $(CFLAGS) -MMD -MP -MF"$(OUTPUT)/$*.d" -o $@ $< + @mv -f $(OUTPUT)/$*.d $(OUTPUT)/$*.d.tmp + @sed -e 's|.*:|$(OUTPUT)/$*.o:|' < $(OUTPUT)/$*.d.tmp > $(OUTPUT)/$*.d + @sed -e 's/.*://' -e 's/\\$$//' < $(OUTPUT)/$*.d.tmp | fmt -1 | \ + sed -e 's/^ *//' -e 's/$$/:/' >> $(OUTPUT)/$*.d + @rm -f $(OUTPUT)/$*.d.tmp + +# generate a .h file containing the images +$(IMGH): $(IMGS) makeimages + @mkdir -p $(BUILDDIRS) + ./makeimages $(IMGS) > $(IMGH) + +.PHONY: all clean + diff --git a/vncproxy/test/README.images b/vncproxy/test/README.images new file mode 100644 index 0000000..6026d11 --- /dev/null +++ b/vncproxy/test/README.images @@ -0,0 +1,4 @@ +All three substantive test images are public domain images from + http://www.public-domain-image.com/ +Which says +"Public domain images, royalty free stock photos, copyright friendly free images. Not copyrighted, no rights reserved. All pictures on this site are explicitly placed in the public domain, free for any personal or commercial use." diff --git a/vncproxy/test/daisy-glass-art.png b/vncproxy/test/daisy-glass-art.png new file mode 100644 index 0000000..bb87c69 Binary files /dev/null and b/vncproxy/test/daisy-glass-art.png differ diff --git a/vncproxy/test/dandelions-2.png b/vncproxy/test/dandelions-2.png new file mode 100644 index 0000000..6e4158f Binary files /dev/null and b/vncproxy/test/dandelions-2.png differ diff --git a/vncproxy/test/example-httpd-conf-with-ws-proxy b/vncproxy/test/example-httpd-conf-with-ws-proxy new file mode 100644 index 0000000..93e910d --- /dev/null +++ b/vncproxy/test/example-httpd-conf-with-ws-proxy @@ -0,0 +1,48 @@ +CoreDumpDirectory /tmp + + + ServerAdmin noone@example.com + DocumentRoot /path/to/guacamole-0.6.1 + + AddHandler cgi-script .pl + RewriteEngine on + + Options +ExecCGI + Options -Indexes + + LogLevel Debug + +# DBDriver mysql +# DBDParams "host=127.0.0.1 dbname=vnc user=XXXX pass=XXXX" +# +# DBDMin 0 +# DBDKeep 0 +# DBDMax 1 +# DBDExptime 300 +# DBDPersist Off + + + RequestReadTimeout body=300,minrate=1 + + + + # Satisfy any + # AuthType none + # Require all granted + SetHandler websocket-handler + WebSocketHandler /usr/lib/apache2/modules/mod_websocket_vnc_proxy.so vnc_proxy_init + WebSocketTcpProxyBase64 off + WebSocketTcpProxyHost 127.0.0.1 + WebSocketTcpProxyPort 4823 + WebSocketTcpProxyProtocol guacamole + WebSocketTcpProxyGuacamole on +# WebSocketTcpProxyQuery "SELECT vnc_server_node AS hostname, vnc_server_port AS port, vnc_proxy_ip AS connecthost, vnc_proxy_port AS connectport, '' AS encodings, 'XXXX' AS password, 0 AS swap_read_blue, 0 AS read_only FROM vnc_session WHERE session_key=%s" +# WebSocketTcpProxySendInitialData on +# WebSocketTcpProxySecret XXXXX + WebSocketTcpProxyTimeout 3600 + # WebSocketTcpProxyLocalIP 192.168.250.142 + + + + + diff --git a/vncproxy/test/guactest.c b/vncproxy/test/guactest.c new file mode 100644 index 0000000..29de7d2 --- /dev/null +++ b/vncproxy/test/guactest.c @@ -0,0 +1,537 @@ +/* + * vncproxy + * + * (c) 2011 Flexiant Limited + * + */ + +#include "guactest.h" +#include "list.h" +#include "utils.h" +#include "../build/guactest/image.h" + +DEBUGFILE; + +typedef struct gtconnection +{ + int wsproxyfd; + int doneinit; + int packet; + int needsync; + int sendsync; + int lastsynctx; + int lastsyncrx; + ssize_t offset; + struct timeval lastsynctime; + struct timeval activetime; + struct gtconnection *prev; + struct gtconnection *next; +} gtconnection_t; + +DECLARE_LIST (gtconnection_t); +DEFINE_LIST (gtconnection_t); + +/* Set by the signal handler */ +volatile sig_atomic_t master_rxsig_quit = 0; +volatile sig_atomic_t master_rxsig_reread = 0; +volatile sig_atomic_t master_rxsig_process = 0; +volatile sig_atomic_t master_rxsig_pipe = 0; + +int listenport = 4823; +int timeout = 30; +int doimages = 0; +int imgloops=1; + +#define DATALENGTH 65536 +#define MAXCMDLENGTH 128 + +char * initialdata = "5.reset,1.0;4.size,1.0,4.1024,3.768;5.reset,1.0;3.png,1.0,1.0,1.0,1.0," IMAGE_SINGLE_PIXEL ";"; +char * subsequentdata = NULL; + +char * images [] = {IMAGE_DAISY_GLASS_ART, IMAGE_DANDELIONS_2, IMAGE_RED_TULIPS_FLOWER}; + +gtconnection_t_list_t gtconnectionlist; + +int +plen(int v) +{ + int k; + int j=10; + if (v<0) + return 1+plen(-v); + for (k=1;;k++,j*=10) + if (v MAXCMDLENGTH); ) + { + int w=1+random() % 512; + int h=1+random() % 384; + /* x ranges from -w+1 to 1023, y from -h+1 to 768 */ + int x=-w+random() % (1024+w); + int y=-h+random() % (768+h); + int r=random() % 256; + int g=random() % 256; + int b=random() % 256; + int a=random() % 256; + p+=snprintf(p, space, "4.rect,1.0,%d.%d,%d.%d,%d.%d,%d.%d;", + plen(x),x, + plen(y),y, + plen(w),w, + plen(h),h); + p+=snprintf(p, space, "5.cfill,1.0,1.0,%d.%d,%d.%d,%d.%d,%d.%d;", + plen(r),r, + plen(g),g, + plen(b),b, + plen(a),a); + } + } +} + +gtconnection_t * +gtconnection_new () +{ + gtconnection_t *gc = calloc (1, sizeof (struct gtconnection)); + if (!gc) + return NULL; + gc->wsproxyfd = -1; + gc->offset = 0; + return gc; +} + +void +gtconnection_free (gtconnection_t * gc) +{ + /* first dump the buffers */ + dolog (LOG_DEBUG, "gtconnection_free: called"); + + free (gc); + return; +} + +int +gtconnection_close (gtconnection_t * gc) +{ + if (gc->wsproxyfd >= 0) + { + shutdown (gc->wsproxyfd, SHUT_RDWR); + close (gc->wsproxyfd); + gc->wsproxyfd = -1; + } + return 0; +} + +void +gtconnection_delete (gtconnection_t * gc) +{ + gtconnection_close (gc); + gtconnection_free (gc); +} + +void +gtconnection_accept (int listenfd) +{ + struct sockaddr_in saddr; + socklen_t salen = sizeof (saddr); /* not large enough for unix domain sockets but that's OK */ + struct gtconnection *mc = NULL; + int fd; + + if (listenfd < 0) + return; + + if (-1 == (fd = accept (listenfd, (struct sockaddr *) &saddr, &salen))) + { + dolog (LOG_ERR, "Master: Could not accept a new connection"); + /* This might have been that the connection has disappeared before we got here, but + * there is a risk of a busy-loop here so sleep + */ + usleep (1000); + return; + } + + int flags = -1; + if (-1 == (flags = fcntl (fd, F_GETFL, 0))) + { + dolog (LOG_ERR, "gtconnection_accept: fcntl F_GETFL failed"); + close (fd); + return; + } + + if (-1 == fcntl (fd, F_SETFL, flags | O_NONBLOCK)) + { + dolog (LOG_ERR, "gtconnection_accept: fcntl F_SETFL failed"); + close (fd); + return; + } + + if (NULL == (mc = gtconnection_new ())) + { + dolog (LOG_ERR, + "gtconnection_accept: Could not allocate a new connection"); + close (fd); + return; + } + + mc->wsproxyfd = fd; + gettimeofday (&(mc->activetime), NULL); + gettimeofday (&(mc->lastsynctime), NULL); + + gtconnection_t_list_addtail (>connectionlist, mc); + return; +} + +void +handlesignal (int sig) +{ + /* DO NOT dolog() in here as the logging mutex may already be held */ + switch (sig) + { + case SIGINT: + case SIGTERM: + master_rxsig_quit++; + break; + case SIGHUP: + master_rxsig_reread++; + break; + case SIGCHLD: + /* do all our waiting here */ + while (1) + { + pid_t pid; + int status; + pid = waitpid (WAIT_ANY, &status, WNOHANG); + if (pid < 0) + { + break; + } + if (pid == 0) + break; + /* pid has terminated */ + } + break; + case SIGPIPE: + master_rxsig_pipe++; + break; + default: + break; + } +} + +int +domasterselectsignals () +{ + /* process signals */ + if (master_rxsig_pipe) + { + dolog (LOG_DEBUG, "SIGPIPE received"); + master_rxsig_pipe = 0; + } + if (master_rxsig_reread) + { + master_rxsig_reread = 0; + /* configreread (); */ + } + return (master_rxsig_quit); +} + + +void +mastermainloop () +{ + fd_set readfds; + fd_set writefds; + struct timeval lastread; + struct timeval now; + struct timeval elapsed; + sigset_t set; + struct sigaction sa; + int masterlistentcpfd = -1; + + gettimeofday (&lastread, NULL); + + if (-1 == (masterlistentcpfd = tcp_listen_connection (listenport))) + { + perror ("Could not listen on tcp master port"); + exit (1); + } + + int flags = -1; + if (-1 == (flags = fcntl (masterlistentcpfd, F_GETFL, 0))) + { + close (masterlistentcpfd); + dolog (LOG_ERR, "fcntl F_GETFL failed"); + exit (1); + } + + if (-1 == fcntl (masterlistentcpfd, F_SETFL, flags | O_NONBLOCK)) + { + close (masterlistentcpfd); + dolog (LOG_ERR, "fcntl F_SETFL failed"); + exit (1); + } + + /* block all signals */ + sigfillset (&set); + pthread_sigmask (SIG_BLOCK, &set, NULL); + + /* Set up the structure to specify the new action. */ + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = handlesignal; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + sigaction (SIGPIPE, &sa, NULL); + sigaction (SIGHUP, &sa, NULL); + sigaction (SIGUSR1, &sa, NULL); + sigaction (SIGUSR2, &sa, NULL); + sigaction (SIGCHLD, &sa, NULL); + + sigemptyset (&set); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGTERM); + sigaddset (&set, SIGPIPE); + sigaddset (&set, SIGHUP); + sigaddset (&set, SIGUSR1); + sigaddset (&set, SIGUSR2); + sigaddset (&set, SIGCHLD); + pthread_sigmask (SIG_UNBLOCK, &set, NULL); + + master_rxsig_quit = 0; + + do + { + master_rxsig_pipe = 0; + + do + { + int maxfd = 0; + int periodic = 0; + int quit = 0; + int result = 0; + int selecterrno = 0; + struct gtconnection *mc = NULL; + struct gtconnection *nmc = NULL; + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + + /* Leave 10 fds spare for logging etc */ + if (gtconnection_t_list_getitems (>connectionlist) * 2 < + FD_SETSIZE - 10) + fd_add_with_max (masterlistentcpfd, &maxfd, &readfds); + + gettimeofday (&now, NULL); + for (mc = gtconnection_t_list_gethead (>connectionlist); mc; + mc = mc->next) + { + /* Now communication between the VM and Wsproxy */ + if (mc->wsproxyfd >= 0) + { + if (!timeval_subtract (&elapsed, &now, &(mc->lastsynctime)) + && (elapsed.tv_sec >= 2)) + mc->lastsyncrx = mc->lastsynctx; + + if (mc->lastsynctx > mc->lastsyncrx) + dolog(LOG_DEBUG, "Waiting as lastsynctx = %d lastsyncrx = %d", + mc->lastsynctx, + mc->lastsyncrx); + else + fd_add_with_max (mc->wsproxyfd, &maxfd, &writefds); + fd_add_with_max (mc->wsproxyfd, &maxfd, &readfds); + } + } + + /* Repeat select whilst EINTR happens */ + do + { + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + result = + select (1 + maxfd, &readfds, &writefds, NULL, &timeout); + + selecterrno = errno; + + /* process signals */ + quit = domasterselectsignals (); + } + while ((result == -1) && (selecterrno == EINTR) && !quit); + + if (!quit) + { + struct timeval now; + struct timeval elapsed; + /* if more than one second has passed, do periodic jobs */ + gettimeofday (&now, NULL); + if (!timeval_subtract (&elapsed, &now, &lastread) + && (elapsed.tv_sec >= 1)) + { + lastread = now; + periodic = 1; + } + } + + if (master_rxsig_quit) + break; + + /* Process new connections */ + if (FD_ISSET (masterlistentcpfd, &readfds)) + { + gtconnection_accept (masterlistentcpfd); + } + + gettimeofday (&now, NULL); + for (mc = gtconnection_t_list_gethead (>connectionlist); mc; + mc = nmc) + { + nmc = mc->next; + + if ((mc->wsproxyfd >= 0) && FD_ISSET (mc->wsproxyfd, &readfds)) + { + char buf[1025]; + buf[1024]=0; + gettimeofday (&(mc->activetime), NULL); + ssize_t got = 0; + if ((got = read (mc->wsproxyfd, buf, sizeof(buf)-1))<=0) + { + dolog (LOG_DEBUG, + "Read from fd returned 0 bytes, closing connection"); + gtconnection_t_list_unlink (>connectionlist, mc); + gtconnection_delete (mc); + continue; + } + buf[got]=0; + char * s = buf; + if (NULL != (s = strstr(buf,"sync"))) + { + char * p; + for (p = s; *p && (*p != ';'); p++) {} + *p=0; + p = s+5; + while (*p && (*p != '.')) + p++; + p++; + mc->lastsyncrx = atoi(p); + dolog (LOG_DEBUG, "Got %s lastsyncrx=%d p=%s", s, mc->lastsyncrx, p); + mc->needsync=0; + } + dolog (LOG_DEBUG, "Got '%s'", buf); + } + + if ((mc->wsproxyfd >= 0) && FD_ISSET (mc->wsproxyfd, &writefds)) + { + gettimeofday (&(mc->activetime), NULL); + char syncbuf[20]; + snprintf(syncbuf, 20, "4.sync,%d.%d;", plen(mc->lastsynctx+1), mc->lastsynctx+1); + mc->packet++; + char * buf = (mc->sendsync)?syncbuf:((mc->doneinit)?subsequentdata:initialdata); + ssize_t len = strlen(buf); + if (mc->sendsync) + { + dolog (LOG_DEBUG, + "writing sync %s", buf); + mc->lastsynctx++; + } + ssize_t written = -1; + ssize_t towrite = len - mc->offset; + if ((written = write(mc->wsproxyfd, buf + mc->offset, towrite))<0) + { + dolog (LOG_DEBUG, + "vncbuf_writetofd to websocket returned error, closing connection"); + gtconnection_t_list_unlink (>connectionlist, mc); + gtconnection_delete (mc); + continue; + } + mc->offset += written; + if (mc->offset >= len) + { + dolog (LOG_DEBUG, "Wrapping"); + mc->offset = 0; + mc->doneinit = 1; + if (mc->sendsync) + { + gettimeofday (&(mc->lastsynctime), NULL); + makedata(); + } + mc->needsync = mc->sendsync; + mc->sendsync = !(mc->sendsync); + } + } + + if (!timeval_subtract (&elapsed, &now, &(mc->activetime)) + && (elapsed.tv_sec >= timeout)) + { + dolog (LOG_INFO, + "mastermainloop: connection idle too long"); + gtconnection_t_list_unlink (>connectionlist, mc); + gtconnection_delete (mc); + } + } + + } + while (!master_rxsig_quit); + + /* Do deinit here for stuff where we need a cf file reread */ + + } + while (!master_rxsig_quit); + + if (masterlistentcpfd != -1) + { + close (masterlistentcpfd); + masterlistentcpfd = -1; + } +} + + +int +main (int argc, char **argv) +{ + processdebugoptions("7"); + + if ((argc>1) && !strcmp(argv[1], "-i")) + doimages = 1; + + startsyslog (); + + dolog (LOG_NOTICE, "Starting up"); + + subsequentdata = calloc (1, doimages? + (imgloops*((strlen(images[0])+strlen(images[1])+strlen(images[2]))+MAXCMDLENGTH+1)) + :(DATALENGTH+MAXCMDLENGTH+1)); + makedata(); + + mastermainloop (); + + dolog (LOG_NOTICE, "Exiting\n"); + + fflush (stdout); + fflush (stderr); + + exit (0); +} diff --git a/vncproxy/test/guactest.h b/vncproxy/test/guactest.h new file mode 100644 index 0000000..0558f20 --- /dev/null +++ b/vncproxy/test/guactest.h @@ -0,0 +1,44 @@ +/* + * vncproxy + * + * (c) 2011 Flexiant Limited + * + */ + +#ifndef _GAUCTEST_GAUCTEST_H +#define _GAUCTEST_GAUCTEST_H + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 500 +#define _BSD_SOURCE + +/* System files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* #ifndef _GAUCTEST_GAUCTEST_H */ diff --git a/vncproxy/test/list.h b/vncproxy/test/list.h new file mode 100644 index 0000000..ce2cf1e --- /dev/null +++ b/vncproxy/test/list.h @@ -0,0 +1,201 @@ +/* + * vncproxy + * + * (c) 2011 Flexiant Limited + * + */ + +#ifndef _GAUCTEST_LIST_H +#define _GAUCTEST_LIST_H + +#define DECLARE_LIST2(ITEMTYPE, LISTNAME) \ + \ + typedef struct ITEMTYPE ## _ ## LISTNAME \ + { \ + ITEMTYPE * listhead; \ + ITEMTYPE * listtail; \ + int numitems; \ + } ITEMTYPE ## _ ## LISTNAME ## _t; \ + \ + int ITEMTYPE ## _ ## LISTNAME ## _getitems(struct ITEMTYPE ## _ ## LISTNAME * l); \ + ITEMTYPE * ITEMTYPE ## _ ## LISTNAME ## _gethead(struct ITEMTYPE ## _ ## LISTNAME * l); \ + ITEMTYPE * ITEMTYPE ## _ ## LISTNAME ## _gettail(struct ITEMTYPE ## _ ## LISTNAME * l); \ + void ITEMTYPE ## _ ## LISTNAME ## _unlink(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p); \ + void ITEMTYPE ## _ ## LISTNAME ## _addtail(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p); \ + void ITEMTYPE ## _ ## LISTNAME ## _addhead(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p); \ + void ITEMTYPE ## _ ## LISTNAME ## _addbefore(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * c, ITEMTYPE * p); \ + void ITEMTYPE ## _ ## LISTNAME ## _addafter(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * c, ITEMTYPE * p) +/* NOTE ABSENCE OF FINAL SEMICOLON */ + +#define DECLARE_LIST(ITEMTYPE) DECLARE_LIST2(ITEMTYPE, list) +#define DECLARE_SECONDARY_LIST(ITEMTYPE, PREFIX) DECLARE_LIST2(ITEMTYPE, PREFIX ## list) + +#define DEFINE_LIST2(ITEMTYPE, LISTNAME, NEXTNAME, PREVNAME) \ + \ + int ITEMTYPE ## _ ## LISTNAME ## _getitems(struct ITEMTYPE ## _ ## LISTNAME * l) \ + { \ + return l?l->numitems:0; \ + } \ + \ + ITEMTYPE * ITEMTYPE ## _ ## LISTNAME ## _gethead(struct ITEMTYPE ## _ ## LISTNAME * l) \ + { \ + return l?l->listhead:NULL; \ + } \ + \ + ITEMTYPE * ITEMTYPE ## _ ## LISTNAME ## _gettail(struct ITEMTYPE ## _ ## LISTNAME * l) \ + { \ + return l?l->listtail:NULL; \ + } \ + \ + void ITEMTYPE ## _ ## LISTNAME ## _unlink(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p) \ + { \ + if (p && l) \ + { \ + ITEMTYPE * PREVNAME = p->PREVNAME; \ + ITEMTYPE * NEXTNAME = p->NEXTNAME; \ + \ + /* Fix link to PREVNAMEious */ \ + if (PREVNAME) \ + { \ + PREVNAME->NEXTNAME = NEXTNAME; \ + } \ + else \ + { \ + l->listhead = NEXTNAME; \ + } \ + \ + if (NEXTNAME) \ + { \ + NEXTNAME->PREVNAME = PREVNAME; \ + } \ + else \ + { \ + l->listtail = PREVNAME; \ + } \ + \ + p->PREVNAME = NULL; \ + p->NEXTNAME = NULL; \ + l->numitems--; \ + } \ + } \ + \ + /* Add a new list item to the tail */ \ + void ITEMTYPE ## _ ## LISTNAME ## _addtail(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p) \ + { \ + if (!p || !l) \ + return; \ + if (l->listtail) \ + { \ + if (l->listtail->NEXTNAME) \ + { \ + dolog(LOG_ERR,"ERROR: " #ITEMTYPE "_ ## LISTNAME ## _addtail found list tail has a NEXTNAME pointer"); \ + } \ + l->listtail->NEXTNAME = p; \ + p->NEXTNAME = NULL; \ + p->PREVNAME = l->listtail; \ + l->listtail = p; \ + } \ + else \ + { \ + if (l->listhead) \ + { \ + dolog(LOG_ERR,"ERROR: " #ITEMTYPE "_ ## LISTNAME ## _addtail found no list tail but a list head"); \ + } \ + l->listhead = p; \ + l->listtail = p; \ + p->PREVNAME = NULL; \ + p->NEXTNAME = NULL; \ + } \ + \ + l->numitems++; \ + } \ + \ + /* Add a new list item to the head */ \ + void ITEMTYPE ## _ ## LISTNAME ## _addhead(struct ITEMTYPE ## _ ## LISTNAME * l, ITEMTYPE * p) \ + { \ + if (!p || !l) \ + return; \ + if (l->listhead) \ + { \ + if (l->listhead->PREVNAME) \ + { \ + dolog(LOG_ERR,"ERROR: " #ITEMTYPE "_ ## LISTNAME ## _addhead found list head has a PREVNAME pointer"); \ + } \ + l->listhead->PREVNAME = p; \ + p->PREVNAME = NULL; \ + p->NEXTNAME = l->listhead; \ + l->listhead = p; \ + } \ + else \ + { \ + if (l->listtail) \ + { \ + dolog(LOG_ERR,"ERROR: " #ITEMTYPE "_ ## LISTNAME ## _addhead found no list head but a list tail"); \ + } \ + l->listtail = p; \ + l->listhead = p; \ + p->NEXTNAME = NULL; \ + p->PREVNAME = NULL; \ + } \ + \ + l->numitems++; \ + } \ + \ + /* Add a new list p before current item c */ \ + void ITEMTYPE ## _ ## LISTNAME ## _addbefore(struct ITEMTYPE ## _ ## LISTNAME * l, \ + ITEMTYPE *c, \ + ITEMTYPE * p) \ + { \ + if (!p || !l ) \ + return; \ + if (!c) \ + { \ + ITEMTYPE ## _ ## LISTNAME ## _addtail(l, p); \ + return; \ + } \ + if (!(c->PREVNAME)) \ + { \ + ITEMTYPE ## _ ## LISTNAME ## _addhead(l, p); \ + return; \ + } \ + \ + /* We know c points to an item which is not the list head */ \ + p->PREVNAME = c->PREVNAME; \ + c->PREVNAME->NEXTNAME = p; \ + p->NEXTNAME = c; \ + c->PREVNAME = p; \ + \ + l->numitems++; \ + } \ + \ + /* Add a new list p after current item c */ \ + void ITEMTYPE ## _ ## LISTNAME ## _addafter(struct ITEMTYPE ## _ ## LISTNAME * l, \ + ITEMTYPE *c, \ + ITEMTYPE * p) \ + { \ + if (!p || !l ) \ + return; \ + if (!c) \ + { \ + ITEMTYPE ## _ ## LISTNAME ## _addhead(l, p); \ + return; \ + } \ + if (!(c->NEXTNAME)) \ + { \ + ITEMTYPE ## _ ## LISTNAME ## _addtail(l, p); \ + return; \ + } \ + \ + /* We know c points to an item which is not the list tail */ \ + p->NEXTNAME = c->NEXTNAME; \ + c->NEXTNAME->PREVNAME = p; \ + p->PREVNAME = c; \ + c->NEXTNAME = p; \ + \ + l->numitems++; \ + } \ + +#define DEFINE_LIST(ITEMTYPE) DEFINE_LIST2(ITEMTYPE, list, next, prev) +#define DEFINE_SECONDARY_LIST(ITEMTYPE, PREFIX) DEFINE_LIST2(ITEMTYPE, PREFIX ## list, PREFIX ## next, PREFIX ## prev) + +#endif /* #ifndef _GAUCTEST_LIST_H */ diff --git a/vncproxy/test/makeimages b/vncproxy/test/makeimages new file mode 100755 index 0000000..47da429 --- /dev/null +++ b/vncproxy/test/makeimages @@ -0,0 +1,29 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use MIME::Base64; + +my $file; +my $buf; + +foreach my $fn (@ARGV) +{ + open($file, $fn) or die "$!"; + + my $name = uc($fn); + $name=~s/\.\w*$//g; + $name=~s/^.*\///g; + $name=~s/\W/_/g; + $name="IMAGE_".$name; + + my $s=""; + while (read($file, $buf, 60*57)) + { + $s.=encode_base64($buf,""); + } + + printf "#define $name \"%d.%s\"\n\n", length($s), $s; + + close $file; +} diff --git a/vncproxy/test/red-tulips-flower.png b/vncproxy/test/red-tulips-flower.png new file mode 100644 index 0000000..774554c Binary files /dev/null and b/vncproxy/test/red-tulips-flower.png differ diff --git a/vncproxy/test/single-pixel.png b/vncproxy/test/single-pixel.png new file mode 100644 index 0000000..85e0250 Binary files /dev/null and b/vncproxy/test/single-pixel.png differ diff --git a/vncproxy/test/utils.c b/vncproxy/test/utils.c new file mode 100644 index 0000000..2419604 --- /dev/null +++ b/vncproxy/test/utils.c @@ -0,0 +1,674 @@ +/* + * vncproxy + * + * (c) 2011 Flexiant Limited + * + */ + +#include "guactest.h" +#include "utils.h" +#include "list.h" + +DEBUGFILE; +DECLARE_LIST (debugblock_t); +DEFINE_LIST (debugblock_t); + +debugblock_t_list_t debugblocklist; + +char *progname = "vncproxy"; +int logtosyslog = 0; +int dontdaemonize = 1; + +pthread_mutex_t logmutex = PTHREAD_MUTEX_INITIALIZER; + +struct debugblock * +adddebugblock (struct debugblock *db) +{ + debugblock_t_list_addtail (&debugblocklist, db); + + return db; +} + +static int +strdotcmp (const char *s1, const char *s2) +{ + char c1; + char c2; + do + { + c1 = *s1++; + if (c1 == '.') + c1 = 0; + c2 = *s2++; + if (c2 == '.') + c2 = 0; + if (c1 != c2) + return -1; + } + while (c1 && c2); + return 0; +} + +static const char *debuglevels[] = { + "EMERG", + "ALERT", + "CRIT", + "ERR", + "WARNING", + "NOTICE", + "INFO", + "DEBUG", + NULL +}; + +static int +stringtolevel (const char *s) +{ + int l; + for (l = 0; debuglevels[l]; l++) + if (!strcasecmp (s, debuglevels[l])) + return l; + return atoi (s); +} + +int +processdebugoptions (char *options) +{ + char *saveptr = NULL; + char *delims = ",;"; + char *opt = NULL; + char *optionsdup = strdup (options); + int defaultlevel = LOG_DEBUG; + struct debugblock *db; + + /* Set all log levels to -1 */ + for (db = debugblock_t_list_gethead (&debugblocklist); db; db = db->next) + db->loglevel = -1; + + /* while (NULL != (opt = strtok_r (opt?NULL:options, delims, &saveptr))) */ + while (NULL != (opt = strtok_r (opt ? NULL : optionsdup, delims, &saveptr))) + { + char *equals = strchr (opt, '='); + if (equals) + { + *equals = 0; + for (db = debugblock_t_list_gethead (&debugblocklist); db; + db = db->next) + if (!strdotcmp (opt, db->targetname)) + { + db->loglevel = stringtolevel (equals + 1); /* at worst the original NULL */ + *equals = '='; + /* do not break; as we may have several of these due to + the same debug block in several files */ + } + if (!*equals) + { + fprintf (stderr, "Unknown debug target: %s\n", opt); + free (optionsdup); + return -1; + } + } + else + defaultlevel = stringtolevel (opt); + } + + for (db = debugblock_t_list_gethead (&debugblocklist); db; db = db->next) + if (db->loglevel == -1) + { + db->loglevel = defaultlevel; + } + + free (optionsdup); + return 0; +} + +/* Log function */ + +void +dolog_internal (struct debugblock *db, int priority, const char *fmt, ...) +{ +#define MAXMESSAGE 2048 +#define TRUNCATION "...[truncated]" + va_list ap; + char message[MAXMESSAGE + sizeof (TRUNCATION) + 1]; + struct timeval tv; + struct tm tm; + char tstring[256]; + + message[0] = '\0'; + va_start (ap, fmt); + vsnprintf (message, MAXMESSAGE, fmt, ap); + va_end (ap); + + /* allow the string to flow through to our truncation message */ + message[MAXMESSAGE - 1] = ' '; + strcpy (message + MAXMESSAGE, TRUNCATION); + + pthread_mutex_lock (&logmutex); + + if (logtosyslog) + { + syslog (priority, "%s: %s", db->targetname, message); + pthread_mutex_unlock (&logmutex); + return; + } + + /* Do something different if daemonized here */ + + gettimeofday (&tv, NULL); + localtime_r (&tv.tv_sec, &tm); + strftime (tstring, sizeof (tstring), "%Y-%m-%d %H:%M:%S", &tm); + fprintf (stderr, "%s.%06d ", tstring, (int) tv.tv_usec); + + fprintf (stderr, "%s: %s", db->targetname, message); + if (*message) + { + if (message[strlen (message) - 1] != '\n') + fputc ('\n', stderr); + } + + pthread_mutex_unlock (&logmutex); + +} + +void +startsyslog () +{ + if (!dontdaemonize) + { + openlog (progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + logtosyslog = 1; + } +} + +void +stopsyslog () +{ + if (logtosyslog) + { + closelog (); + logtosyslog = 0; + } +} + +int +timeval_subtract (struct timeval *result, struct timeval *x, + struct timeval *y) +{ + if (x->tv_usec < y->tv_usec) + { + int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; + y->tv_usec -= 1000000 * nsec; + y->tv_sec += nsec; + } + + if (x->tv_usec - y->tv_usec > 1000000) + { + int nsec = (x->tv_usec - y->tv_usec) / 1000000; + y->tv_usec += 1000000 * nsec; + y->tv_sec -= nsec; + } + + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_usec = x->tv_usec - y->tv_usec; + + return x->tv_sec < y->tv_sec; +} + +void +gettimeout (struct timespec *ts, int seconds) +{ + struct timeval now; + gettimeofday (&now, NULL); + ts->tv_sec = now.tv_sec + seconds; + ts->tv_nsec = now.tv_usec * 1000; +} + +void +gettimeoutms (struct timespec *ts, int ms) +{ + struct timeval now; + gettimeofday (&now, NULL); + ts->tv_sec = now.tv_sec; + ts->tv_nsec = now.tv_usec * 1000LL + ms * 1000000LL; + while (ts->tv_nsec >= 1000000000L) + { + ts->tv_sec++; + ts->tv_nsec -= 1000000000L; + } +} + +/* + * This is a safe implementaton of the system() function + */ +int +safesystem (char *command, char *const cargv[], int *status, char *pipedata) +{ + sigset_t set; + struct sigaction sa; + int pipefd[2] = { -1, -1 }; + + const char *devnull = "/dev/null"; + + if (pipedata) + { + if (pipe (pipefd) == -1) + { + dolog (LOG_ERR, "Critical: pipe() error\n"); + return -1; + } + } + + int i = fork (); + if (i < 0) + { + dolog (LOG_ERR, "Critical: fork() error\n"); + if (pipedata) + { + close (pipefd[0]); + close (pipefd[1]); + } + return -1; + } + + if (i > 0) + { + /* We are the parent */ + if (pipedata) + { + close (pipefd[0]); /* close read end of pipe */ + + /* do the write */ + write (pipefd[1], pipedata, strlen (pipedata)); + close (pipefd[1]); + } + waitpid (i, status, 0); + /* ignore the result, restore the signal handler */ + return 0; + } + + setsid (); + chdir ("/"); + + for (i = 0; cargv[i]; i++) + dolog (LOG_DEBUG, "Parameter %s", cargv[i]); + + if (pipedata) + dolog (LOG_DEBUG, "STDIN = %s", pipedata); + + for (i = getdtablesize () - 1; i >= 0; i--) + { + if (!pipedata || (i != pipefd[0])) + close (i); + } + + i = open (devnull, O_RDWR); + if (i == -1) + { + fprintf (stderr, "Unable to open /dev/null\n"); + _exit (1); + } + + if (pipedata) + { + dup2 (pipefd[0], 0); + } + else + { + i = open (devnull, O_RDONLY); + if (i != 0) + { + dup2 (i, 0); + close (i); + } + } + + i = open (devnull, O_WRONLY); + if (i != 1) + { + dup2 (i, 1); + close (i); + } + + i = open (devnull, O_WRONLY); + if (i != 2) + { + dup2 (i, 2); + close (i); + } + + /* Set up the structure to specify the new action. */ + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = SIG_DFL; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + sigaction (SIGPIPE, &sa, NULL); + sigaction (SIGCHLD, &sa, NULL); + sigaction (SIGHUP, &sa, NULL); + sigaction (SIGUSR1, &sa, NULL); + sigaction (SIGUSR2, &sa, NULL); + + /* unblock all signals */ + sigfillset (&set); + pthread_sigmask (SIG_UNBLOCK, &set, NULL); + + + execv (command, cargv); + _exit (1); + return -1; /* never reached */ +} + +void +fd_add_with_max (int fd, int *max, fd_set * fds) +{ + if ((fd < 0) || !max || !fds) + return; + FD_SET (fd, fds); + if (fd > *max) + *max = fd; + return; +} + +int +delete_directory_recursive_fn (const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + return (typeflag == FTW_D) ? rmdir (fpath) : unlink (fpath); +} + +int +delete_directory_recursive (char *path) +{ + return nftw (path, &delete_directory_recursive_fn, 10, + FTW_DEPTH | FTW_PHYS); +} + + +int +ensure_directory (char *path, mode_t mode) +{ + struct stat s; + int ret; + ret = stat (path, &s); + if (ret < 0) + { + if (errno == ENOENT) + return mkdir (path, mode); + else + return -1; + } + + if (S_ISDIR (s.st_mode)) + return 0; + + errno = EEXIST; + return -1; +} + +/* + * This does roughly the equivalent of mkdir -p, i.e. ensures the path + * to a directory exists. If final is set, then the path itself is a directory + * else it the path is an object that is to be stored in the directory + */ +int +ensure_directory_recursive (char *path, mode_t mode, int final) +{ + char *start; + char *pdup; + char *slash; + + /* make a copy of path as we are to modify it */ + pdup = strdup (path); + if (!pdup) + { + errno = ENOMEM; + return -1; + } + + start = pdup; + + + while (1) + { + slash = strchr (start, '/'); + if (!slash) + { + /* we are on the last component */ + free (pdup); + if (final) + return ensure_directory (path, mode); + else + return 0; + } + if (slash != pdup) + { + *slash = 0; + if (ensure_directory (pdup, mode) < 0) + { + free (pdup); + return -1; + } + *slash = '/'; + } + start = slash + 1; + } + return -1; /* not reached */ +} + +int +testdirwriteable (const char *dir) +{ + char *fn; + int fd; + if (asprintf (&fn, "%s/.test", dir) < 0) + { + errno = ENOMEM; + return -1; + } + + if ((fd = open (fn, O_CREAT | O_RDWR, 0644)) < 0) + { + free (fn); + return -1; + } + + close (fd); + if (unlink (fn) < 0) + { + free (fn); + return -1; + } + + free (fn); + return 0; +} + +int +writememtofileatomic (void *mem, size_t count, char *fn) +{ + char *tempfn; + int fd; + if (-1 == (asprintf (&tempfn, "%s%s", fn, ".tmp"))) + { + dolog (LOG_CRIT, "Could not allocate temporary file name"); + return -1; + } + + if ((fd = open (tempfn, O_RDWR | O_CREAT, 0644)) == -1) + { + dolog (LOG_CRIT, "Could not open file to write: %m"); + free (tempfn); + return -1; + } + + if (write (fd, mem, count) < 0) + { + dolog (LOG_CRIT, "Could not write: %m"); + close (fd); + free (tempfn); + return -1; + } + + close (fd); + + if (rename (tempfn, fn) < 0) + { + dolog (LOG_CRIT, "Could not rename: %m"); + free (tempfn); + return -1; + } + + free (tempfn); + return 0; +} + +uint64_t +getsize (char *arg) +{ + uint64_t param = 0; + char *end, *found; + const char *suffix = "bkmgtpe"; + param = strtoull (arg, &end, 10); + + if (*end == '\0') + { + /* param is right - do nothing */ + } + else if ((found = strchr (suffix, tolower (*end)))) + { + param <<= (10 * (found - suffix)); + } + else + { + dolog (LOG_CRIT, "Bad parameter\n"); + exit (1); + } + return param; +} + +/* Like memcmp, returns 0 if a block of memory is zero */ +int +memcmpzero (const void *s, size_t n) +{ + const char *cp; + const uint64_t *up; + + /* first align to 8 byte boundary */ + for (cp = s; (n > 0) && (((uintptr_t) cp) & 7); n--) + { + if (*(cp++)) + return 1; + } + + /* Now work 8 bytes at a time */ + for (up = (uint64_t *) cp; (n >= 8); n -= 8) + { + if (*(up++)) + return 1; + } + + /* Now check the remaining stuff byte by byte */ + for (cp = (char *) up; n > 0; n--) + { + if (*(cp++)) + return 1; + } + + return 0; +} + +/* Initialise the listen socket for connections, return the fd */ +int +tcp_listen_connection (int port) +{ + int listenfd; + struct sockaddr_in listenaddr; + int one = 1; + + if (-1 == (listenfd = socket (AF_INET, SOCK_STREAM, 0))) + { + dolog (LOG_CRIT, "open() Could not create listen socket"); + return -1; + } + + if (-1 == + setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one))) + { + dolog (LOG_CRIT, "open() Could not do SO_REUSEADDR"); + close (listenfd); + return -1; + } + + /* Zero out socket data, then set it up */ + memset (&listenaddr, 0, sizeof (listenaddr)); + listenaddr.sin_family = AF_INET; + listenaddr.sin_addr.s_addr = htonl (INADDR_ANY); + listenaddr.sin_port = htons (port); + + if (-1 == + bind (listenfd, (struct sockaddr *) &listenaddr, sizeof (listenaddr))) + { + dolog (LOG_CRIT, "open() Could not bind listen socket"); + close (listenfd); + return -1; + } + + if (-1 == listen (listenfd, 100)) + { + dolog (LOG_CRIT, "open() Could not listen on listen socket"); + close (listenfd); + return -1; + } + + return listenfd; +} + +/* Initialise the listen socket for connections, return the fd */ +int +unix_listen_connection (char *path) +{ + int listenfd; + struct sockaddr_un *listenaddr; + int len; + len = sizeof (sa_family_t) + strlen (path) + 1; + + if (NULL == (listenaddr = calloc (1, len))) + { + dolog (LOG_CRIT, "could not allocate the listen socket"); + exit (1); + } + + unlink (path); /* ignore errors */ + + if (-1 == (listenfd = socket (AF_UNIX, SOCK_STREAM, 0))) + { + dolog (LOG_CRIT, "open() Could not create listen socket"); + free (listenaddr); + return -1; + } + + /* Zero out socket data, then set it up */ + listenaddr->sun_family = AF_UNIX; + strcpy (listenaddr->sun_path, path); /* length checked on allocation */ + + if (-1 == bind (listenfd, (struct sockaddr *) listenaddr, len)) + { + dolog (LOG_CRIT, "open() Could not bind listen socket"); + free (listenaddr); + close (listenfd); + return -1; + } + + free (listenaddr); + + if (-1 == listen (listenfd, 100)) + { + dolog (LOG_CRIT, "open() Could not listen on listen socket"); + close (listenfd); + return -1; + } + + return listenfd; +} diff --git a/vncproxy/test/utils.h b/vncproxy/test/utils.h new file mode 100644 index 0000000..df4b913 --- /dev/null +++ b/vncproxy/test/utils.h @@ -0,0 +1,166 @@ +/* + * vncproxy + * + * (c) 2011 Flexiant Limited + * + */ + +#ifndef _GAUCTEST_UTILS_H +#define _GAUCTEST_UTILS_H + +typedef struct debugblock +{ + char *targetname; + int loglevel; + struct debugblock *next; + struct debugblock *prev; +} debugblock_t; + +/* Note this is a separate static variable per file */ +static struct debugblock *pfiledebugblock = NULL; /* NB - per file variable */ + +int processdebugoptions (char *options); +void dolog_internal (struct debugblock *db, int priority, const char *fmt, + ...); +int shouldlog (int priority); +void startsyslog (); +void stopsyslog (); +int timeval_subtract (struct timeval *result, struct timeval *x, + struct timeval *y); +void gettimeout (struct timespec *ts, int seconds); +void gettimeoutms (struct timespec *ts, int ms); +int safesystem (char *command, char *const cargv[], int *status, + char *pipedata); +void fd_add_with_max (int fd, int *max, fd_set * fds); +int delete_directory_recursive (char *path); +int ensure_directory (char *path, mode_t mode); +int ensure_directory_recursive (char *path, mode_t mode, int final); +int testdirwriteable (const char *dir); +struct debugblock *adddebugblock (struct debugblock *db); +int writememtofileatomic (void *mem, size_t count, char *fn); +uint64_t getsize (char *arg); +int memcmpzero (const void *s, size_t n); +int tcp_listen_connection (int port); +int unix_listen_connection (char *path); + +#define DEBUGFILE \ + static struct debugblock filedebugblock = \ + { __FILE__, LOG_NOTICE, NULL, NULL }; \ + __attribute__ ((__constructor__)) \ + static void \ + addfiledebugblock() \ + { \ + pfiledebugblock = adddebugblock(&filedebugblock); \ + } \ + +#define DEBUGBLOCK(d) \ + static struct debugblock_ ## d= { d, LOG_NOTICE, 0, NULL, NULL}; \ + __attribute__ ((__constructor__)) \ + void \ + adddebugblock_ ## d () \ + { \ + adddebugblock(&debugblock_ ## d); \ + } \ + +#define shouldlogdb(db, priority) (db && (db->loglevel >= priority)) + +#define shouldlog(priority) (shouldlogdb(pfiledebugblock, priority)) + +#define dologdb(db, priority, fmt...) \ + do \ + { \ + if (shouldlog(&(debugblock_ ## db))) \ + dolog_internal(&(debugblock ## db), priority, ## fmt ); \ + } while (0) + +#define dolog(priority, fmt...) \ + do \ + { \ + if (shouldlog(priority)) \ + dolog_internal(pfiledebugblock, priority, ## fmt ); \ + } while (0) + + +static inline uint64_t +htonll (uint64_t x) +{ +#ifdef WORDS_BIGENDIAN + return x; +#else + return (((uint64_t) (htonl (((uint32_t *) & x)[0]))) << 32) | + (uint64_t) (htonl (((uint32_t *) & x)[1])); +#endif +} + +#define ntohll htonll + +static inline uint64_t +gettimeofdayus () +{ + struct timeval tv = { 0, 0 }; + gettimeofday (&tv, NULL); + return ((uint64_t) (tv.tv_sec)) * 1000000ULL + (uint64_t) (tv.tv_usec); +} + +#ifdef DEBUG_COND_WAIT +static inline int +pthread_cond_timedwait_dd (pthread_cond_t * cond, pthread_mutex_t * mutex, + const struct timespec *abstime, + const char *condvar, const char *file, + const int line, const char *func) +{ + struct timeval start; + struct timeval stop; + struct timeval diff; + int ret; + dolog (LOG_DEBUG, + "[%08x] pthread_cond_timedwait_d(%s) in %s, abstime=(%d.%09d) at %s:%d starting", + (int) pthread_self (), condvar, func, abstime->tv_sec, + abstime->tv_nsec, file, line); + gettimeofday (&start, NULL); + ret = pthread_cond_timedwait (cond, mutex, abstime); + gettimeofday (&stop, NULL); + timeval_subtract (&diff, &stop, &start); + dolog (LOG_DEBUG, + "[%08x] pthread_cond_timedwait_d(%s) in %s, at %s:%d took %d.%06d seconds, and returned %s", + (int) pthread_self (), condvar, func, file, line, diff.tv_sec, + diff.tv_usec, ret ? strerror (ret) : "[no error]"); + return ret; +} + +static inline int +pthread_cond_broadcast_dd (pthread_cond_t * cond, + const char *condvar, const char *file, + const int line, const char *func) +{ + struct timeval start; + struct timeval stop; + struct timeval diff; + int ret; + gettimeofday (&start, NULL); + ret = pthread_cond_broadcast (cond); + gettimeofday (&stop, NULL); + timeval_subtract (&diff, &stop, &start); + dolog (LOG_DEBUG, + "[%08x] pthread_cond_broadcast_d(%s) in %s, at %s:%d took %d.%06d seconds, and returned %s", + (int) pthread_self (), condvar, func, file, line, diff.tv_sec, + diff.tv_usec, ret ? strerror (ret) : "[no error]"); + return ret; +} + +#define pthread_cond_timedwait_d(x,y,z) pthread_cond_timedwait_dd(x,y,z,#x,__FILE__,__LINE__,__FUNCTION__) +#define pthread_cond_broadcast_d(x) pthread_cond_broadcast_dd(x,#x,__FILE__,__LINE__,__FUNCTION__) + +#else +#define pthread_cond_timedwait_d pthread_cond_timedwait +#define pthread_cond_broadcast_d pthread_cond_broadcast +#endif + +static inline void * +pagealign (void *a, uintptr_t s) +{ + return (void *) ((uintptr_t) a & ~(s - 1)); +} + + +#endif /* #ifndef _GAUCTEST_UTILS_H */