Home | History | Annotate | Download | only in x11vnc
      1 /*
      2    Copyright (C) 2002-2010 Karl J. Runge <runge (at) karlrunge.com>
      3    All rights reserved.
      4 
      5 This file is part of x11vnc.
      6 
      7 x11vnc is free software; you can redistribute it and/or modify
      8 it under the terms of the GNU General Public License as published by
      9 the Free Software Foundation; either version 2 of the License, or (at
     10 your option) any later version.
     11 
     12 x11vnc is distributed in the hope that it will be useful,
     13 but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with x11vnc; if not, write to the Free Software
     19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
     20 or see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, as a special exception, Karl J. Runge
     23 gives permission to link the code of its release of x11vnc with the
     24 OpenSSL project's "OpenSSL" library (or with modified versions of it
     25 that use the same license as the "OpenSSL" library), and distribute
     26 the linked executables.  You must obey the GNU General Public License
     27 in all respects for all of the code used other than "OpenSSL".  If you
     28 modify this file, you may extend this exception to your version of the
     29 file, but you are not obligated to do so.  If you do not wish to do
     30 so, delete this exception statement from your version.
     31 */
     32 
     33 /* -- avahi.c -- */
     34 
     35 #include "x11vnc.h"
     36 #include "connections.h"
     37 #include "cleanup.h"
     38 
     39 void avahi_initialise(void);
     40 void avahi_advertise(char *name, char *host, uint16_t port);
     41 void avahi_reset(void);
     42 void avahi_cleanup(void);
     43 
     44 static pid_t avahi_pid = 0;
     45 
     46 static void kill_avahi_pid(void) {
     47 	if (avahi_pid != 0) {
     48 		rfbLog("kill_avahi_pid: %d\n", (int) avahi_pid);
     49 		kill(avahi_pid, SIGTERM);
     50 		avahi_pid = 0;
     51 	}
     52 }
     53 
     54 static int try_avahi_helper(char *name, char *host, uint16_t port) {
     55 #if LIBVNCSERVER_HAVE_FORK
     56 	char *cmd, *p, *path = getenv("PATH"), portstr[32];
     57 	int i;
     58 
     59 	if (!name || !host || !port) {}
     60 
     61 	/* avahi-publish */
     62 	if (no_external_cmds || !cmd_ok("zeroconf")) {
     63 		return 0;
     64 	}
     65 
     66 	if (!path) {
     67 		return 0;
     68 	}
     69 
     70 	path = strdup(path);
     71 	cmd = (char *) malloc(strlen(path) + 100);
     72 	sprintf(portstr, "%d", (int) port);
     73 
     74 	p = strtok(path, ":");
     75 	while (p) {
     76 		struct stat sbuf;
     77 
     78 		sprintf(cmd, "%s/avahi-publish", p);
     79 		if (stat(cmd, &sbuf) == 0) {
     80 			break;
     81 		}
     82 		sprintf(cmd, "%s/dns-sd", p);
     83 		if (stat(cmd, &sbuf) == 0) {
     84 			break;
     85 		}
     86 		sprintf(cmd, "%s/mDNS", p);
     87 		if (stat(cmd, &sbuf) == 0) {
     88 			break;
     89 		}
     90 		cmd[0] = '\0';
     91 
     92 		p = strtok(NULL, ":");
     93 	}
     94 	free(path);
     95 
     96 	if (!strcmp(cmd, "")) {
     97 		free(cmd);
     98 		rfbLog("Could not find an external avahi/zeroconf helper program.\n");
     99 		return 0;
    100 	}
    101 
    102 	avahi_pid = fork();
    103 
    104 	if (avahi_pid < 0) {
    105 		rfbLogPerror("fork");
    106 		avahi_pid = 0;
    107 		free(cmd);
    108 		return 0;
    109 	}
    110 
    111 	if (avahi_pid != 0) {
    112 		int status;
    113 
    114 		usleep(500 * 1000);
    115 		waitpid(avahi_pid, &status, WNOHANG);
    116 		if (kill(avahi_pid, 0) != 0) {
    117 			waitpid(avahi_pid, &status, WNOHANG);
    118 			avahi_pid = 0;
    119 			free(cmd);
    120 			return 0;
    121 		}
    122 		if (! quiet) {
    123 			rfbLog("%s helper pid is: %d\n", cmd, (int) avahi_pid);
    124 		}
    125 		free(cmd);
    126 		return 1;
    127 	}
    128 
    129 	for (i=3; i<256; i++) {
    130 		close(i);
    131 	}
    132 
    133 	if (strstr(cmd, "/avahi-publish")) {
    134 		execlp(cmd, cmd, "-s", name, "_rfb._tcp", portstr, (char *) NULL);
    135 	} else {
    136 		execlp(cmd, cmd, "-R", name, "_rfb._tcp", ".", portstr, (char *) NULL);
    137 	}
    138 	exit(1);
    139 #else
    140 	if (!name || !host || !port) {}
    141 	return 0;
    142 #endif
    143 }
    144 
    145 #if !defined(LIBVNCSERVER_HAVE_AVAHI) || !defined(LIBVNCSERVER_HAVE_LIBPTHREAD)
    146 void avahi_initialise(void) {
    147 	rfbLog("avahi_initialise: no Avahi support at buildtime.\n");
    148 }
    149 
    150 void avahi_advertise(char *name, char *host, uint16_t port) {
    151 	char *t;
    152 	t = getenv("X11VNC_AVAHI_NAME"); if (t) name = t;
    153 	t = getenv("X11VNC_AVAHI_HOST"); if (t) host = t;
    154 	t = getenv("X11VNC_AVAHI_PORT"); if (t) port = atoi(t);
    155 
    156 	if (!try_avahi_helper(name, host, port)) {
    157 		rfbLog("avahi_advertise:  no Avahi support at buildtime.\n");
    158 		avahi = 0;
    159 	}
    160 }
    161 
    162 void avahi_reset(void) {
    163 	kill_avahi_pid();
    164 	rfbLog("avahi_reset: no Avahi support at buildtime.\n");
    165 }
    166 
    167 void avahi_cleanup(void) {
    168 	kill_avahi_pid();
    169 	rfbLog("avahi_cleanup: no Avahi support at buildtime.\n");
    170 }
    171 #else
    172 
    173 #include <avahi-common/thread-watch.h>
    174 #include <avahi-common/alternative.h>
    175 #include <avahi-client/client.h>
    176 #include <avahi-client/publish.h>
    177 
    178 #include <avahi-common/malloc.h>
    179 #include <avahi-common/error.h>
    180 
    181 
    182 static AvahiThreadedPoll *_poll = NULL;
    183 static AvahiClient *_client = NULL;
    184 static AvahiEntryGroup *_group = NULL;
    185 
    186 static int db = 0;
    187 
    188 typedef struct {
    189 	const char *name;
    190 	const char *host;
    191 	uint16_t port;
    192 } avahi_service_t;
    193 
    194 typedef struct {
    195 	char *name;
    196 	char *host;
    197 	uint16_t port;
    198 } avahi_reg_t;
    199 
    200 #define NREG 16
    201 static avahi_reg_t registered[NREG];
    202 
    203 void avahi_initialise(void) {
    204 	int ret;
    205 	static int first = 1;
    206 
    207 	if (getenv("AVAHI_DEBUG")) {
    208 		db = 1;
    209 	}
    210 	if (first) {
    211 		int i;
    212 		for (i=0; i<NREG; i++) {
    213 			registered[i].name = NULL;
    214 			registered[i].host = NULL;
    215 		}
    216 		first = 0;
    217 	}
    218 
    219 if (db) fprintf(stderr, "in  avahi_initialise\n");
    220 	if (_poll) {
    221 if (db) fprintf(stderr, "    avahi_initialise: poll not null\n");
    222 		return;
    223 	}
    224 
    225 	if (! (_poll = avahi_threaded_poll_new()) ) {
    226 		rfbLog("warning: unable to open Avahi poll.\n");
    227 		return;
    228 	}
    229 
    230 	_client = avahi_client_new(avahi_threaded_poll_get(_poll),
    231 	    0, NULL, NULL, &ret);
    232 	if (! _client) {
    233 		rfbLog("warning: unable to open Avahi client: %s\n",
    234 		    avahi_strerror(ret));
    235 
    236 		avahi_threaded_poll_free(_poll);
    237 		_poll = NULL;
    238 		return;
    239 	}
    240 
    241 	if (avahi_threaded_poll_start(_poll) < 0) {
    242 		rfbLog("warning: unable to start Avahi poll.\n");
    243 		avahi_client_free(_client);
    244 		_client = NULL;
    245 		avahi_threaded_poll_free(_poll);
    246 		_poll = NULL;
    247 		return;
    248 	}
    249 if (db) fprintf(stderr, "out avahi_initialise\n");
    250 }
    251 
    252 static void _avahi_create_services(char *name, char *host,
    253     uint16_t port);
    254 
    255 static void _avahi_entry_group_callback(AvahiEntryGroup *g,
    256     AvahiEntryGroupState state, void *userdata) {
    257 	char *new_name;
    258 	avahi_service_t *svc = (avahi_service_t *)userdata;
    259 
    260 if (db) fprintf(stderr, "in  _avahi_entry_group_callback %d 0x%p\n", state, svc);
    261 	if (g != _group && _group != NULL) {
    262 		rfbLog("avahi_entry_group_callback fatal error (group).\n");
    263 		clean_up_exit(1);
    264 	}
    265 	if (userdata == NULL) {
    266 		rfbLog("avahi_entry_group_callback fatal error (userdata).\n");
    267 		clean_up_exit(1);
    268 	}
    269 
    270 	switch(state) {
    271 	case AVAHI_ENTRY_GROUP_ESTABLISHED:
    272 		rfbLog("Avahi group %s established.\n", svc->name);
    273 #if 0		/* is this the segv problem? */
    274 		free(svc);
    275 #endif
    276 		break;
    277 	case AVAHI_ENTRY_GROUP_COLLISION:
    278 		new_name = avahi_alternative_service_name(svc->name);
    279 		_avahi_create_services(new_name, svc->host, svc->port);
    280 		rfbLog("Avahi Entry group collision\n");
    281 		avahi_free(new_name);
    282 		break;
    283 	case AVAHI_ENTRY_GROUP_FAILURE:
    284 		rfbLog("Avahi Entry group failure: %s\n",
    285 		    avahi_strerror(avahi_client_errno(
    286 		    avahi_entry_group_get_client(g))));
    287 		break;
    288 	default:
    289 		break;
    290 	}
    291 if (db) fprintf(stderr, "out _avahi_entry_group_callback\n");
    292 }
    293 
    294 static void _avahi_create_services(char *name, char *host, uint16_t port) {
    295 	avahi_service_t *svc = (avahi_service_t *)malloc(sizeof(avahi_service_t));
    296 	int ret = 0;
    297 
    298 if (db) fprintf(stderr, "in  _avahi_create_services  '%s' '%s' %d\n", name, host, port);
    299 	svc->name = name;
    300 	svc->host = host;
    301 	svc->port = port;
    302 
    303 	if (!_group) {
    304 if (db) fprintf(stderr, "    _avahi_create_services create group\n");
    305 		_group = avahi_entry_group_new(_client,
    306 		    _avahi_entry_group_callback, svc);
    307 	}
    308 	if (!_group) {
    309 		rfbLog("avahi_entry_group_new() failed: %s\n",
    310 		    avahi_strerror(avahi_client_errno(_client)));
    311 		return;
    312 	}
    313 
    314 	ret = avahi_entry_group_add_service(_group, AVAHI_IF_UNSPEC,
    315 	    AVAHI_PROTO_UNSPEC, 0, name, "_rfb._tcp", NULL, NULL, port, NULL);
    316 	if (ret < 0) {
    317 		rfbLog("Failed to add _rfb._tcp service: %s\n",
    318 		    avahi_strerror(ret));
    319 		return;
    320 	}
    321 
    322 	ret = avahi_entry_group_commit(_group);
    323 	if (ret < 0) {
    324 		rfbLog("Failed to commit entry_group:: %s\n",
    325 		    avahi_strerror(ret));
    326 		return;
    327 	}
    328 if (db) fprintf(stderr, "out _avahi_create_services\n");
    329 }
    330 
    331 void avahi_advertise(char *name, char *host, uint16_t port) {
    332 	int i;
    333 	char *t;
    334 	t = getenv("X11VNC_AVAHI_NAME"); if (t) name = t;
    335 	t = getenv("X11VNC_AVAHI_HOST"); if (t) host = t;
    336 	t = getenv("X11VNC_AVAHI_PORT"); if (t) port = atoi(t);
    337 
    338 if (db) fprintf(stderr, "in  avahi_advertise: '%s' '%s' %d\n", name, host, port);
    339 	if (!_client) {
    340 if (db) fprintf(stderr, "    avahi_advertise client null\n");
    341 		return;
    342 	}
    343 	if (_poll == NULL) {
    344 		rfbLog("Avahi poll not initialized.\n");
    345 		return;
    346 	}
    347 	/* well, we just track it ourselves... */
    348 	for (i=0; i<NREG; i++) {
    349 		if (!registered[i].name) {
    350 			continue;
    351 		}
    352 		if (strcmp(registered[i].name, name)) {
    353 			continue;
    354 		}
    355 		if (strcmp(registered[i].host, host)) {
    356 			continue;
    357 		}
    358 		if (registered[i].port != port) {
    359 			continue;
    360 		}
    361 if (db) fprintf(stderr, "    avahi_advertise already did this one\n");
    362 		return;
    363 	}
    364 	for (i=0; i<NREG; i++) {
    365 		if (!registered[i].name) {
    366 			registered[i].name = strdup(name);
    367 			registered[i].host = strdup(host);
    368 			registered[i].port = port;
    369 			break;
    370 		}
    371 	}
    372 
    373 	avahi_threaded_poll_lock(_poll);
    374 	_avahi_create_services(name, host, port >= 5900 ? port : 5900+port);
    375 	avahi_threaded_poll_unlock(_poll);
    376 if (db) fprintf(stderr, "out avahi_advertise\n");
    377 }
    378 
    379 void avahi_reset(void) {
    380 	int i;
    381 if (db) fprintf(stderr, "in  avahi_reset\n");
    382 	for (i=0; i<NREG; i++) {
    383 		if (registered[i].name) {
    384 			free(registered[i].name);
    385 			registered[i].name = NULL;
    386 		}
    387 		if (registered[i].host) {
    388 			free(registered[i].host);
    389 			registered[i].host = NULL;
    390 		}
    391 	}
    392 	if (!_client || !_group) {
    393 if (db) fprintf(stderr, "    avahi_reset client/group null\n");
    394 		return;
    395 	}
    396 	avahi_entry_group_reset(_group);
    397 	rfbLog("Avahi resetting group.\n");
    398 if (db) fprintf(stderr, "out avahi_reset\n");
    399 }
    400 
    401 static void avahi_timeout (int sig) {
    402 	rfbLog("sig: %d, avahi_cleanup timed out.\n", sig);
    403 	exit(1);
    404 }
    405 
    406 
    407 void avahi_cleanup(void) {
    408 if (db) fprintf(stderr, "in  avahi_cleanup\n");
    409 	if (!_client) {
    410 if (db) fprintf(stderr, "    avahi_cleanup client null\n");
    411 		return;
    412 	}
    413 if (db) fprintf(stderr, "    avahi_cleanup poll_lock\n");
    414 	avahi_threaded_poll_lock(_poll);
    415 if (db) fprintf(stderr, "    avahi_cleanup poll_stop\n");
    416 
    417 	signal(SIGALRM, avahi_timeout);
    418 	alarm(3);
    419 	avahi_threaded_poll_stop(_poll);
    420 	alarm(0);
    421 	signal(SIGALRM, SIG_DFL);
    422 
    423 if (db) fprintf(stderr, "    avahi_cleanup client_free\n");
    424 	avahi_client_free(_client);
    425 	_client = NULL;
    426 
    427 if (db) fprintf(stderr, "    avahi_cleanup poll_free\n");
    428 	avahi_threaded_poll_free(_poll);
    429 	_poll = NULL;
    430 if (db) fprintf(stderr, "out avahi_cleanup\n");
    431 }
    432 
    433 #endif
    434 
    435