1/*
2   Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3   All rights reserved.
4
5This file is part of x11vnc.
6
7x11vnc is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or (at
10your option) any later version.
11
12x11vnc is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with x11vnc; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20or see <http://www.gnu.org/licenses/>.
21
22In addition, as a special exception, Karl J. Runge
23gives permission to link the code of its release of x11vnc with the
24OpenSSL project's "OpenSSL" library (or with modified versions of it
25that use the same license as the "OpenSSL" library), and distribute
26the linked executables.  You must obey the GNU General Public License
27in all respects for all of the code used other than "OpenSSL".  If you
28modify this file, you may extend this exception to your version of the
29file, but you are not obligated to do so.  If you do not wish to do
30so, 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
39void avahi_initialise(void);
40void avahi_advertise(char *name, char *host, uint16_t port);
41void avahi_reset(void);
42void avahi_cleanup(void);
43
44static pid_t avahi_pid = 0;
45
46static 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
54static 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)
146void avahi_initialise(void) {
147	rfbLog("avahi_initialise: no Avahi support at buildtime.\n");
148}
149
150void 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
162void avahi_reset(void) {
163	kill_avahi_pid();
164	rfbLog("avahi_reset: no Avahi support at buildtime.\n");
165}
166
167void 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
182static AvahiThreadedPoll *_poll = NULL;
183static AvahiClient *_client = NULL;
184static AvahiEntryGroup *_group = NULL;
185
186static int db = 0;
187
188typedef struct {
189	const char *name;
190	const char *host;
191	uint16_t port;
192} avahi_service_t;
193
194typedef struct {
195	char *name;
196	char *host;
197	uint16_t port;
198} avahi_reg_t;
199
200#define NREG 16
201static avahi_reg_t registered[NREG];
202
203void 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
219if (db) fprintf(stderr, "in  avahi_initialise\n");
220	if (_poll) {
221if (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	}
249if (db) fprintf(stderr, "out avahi_initialise\n");
250}
251
252static void _avahi_create_services(char *name, char *host,
253    uint16_t port);
254
255static 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
260if (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	}
291if (db) fprintf(stderr, "out _avahi_entry_group_callback\n");
292}
293
294static 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
298if (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) {
304if (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	}
328if (db) fprintf(stderr, "out _avahi_create_services\n");
329}
330
331void 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
338if (db) fprintf(stderr, "in  avahi_advertise: '%s' '%s' %d\n", name, host, port);
339	if (!_client) {
340if (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		}
361if (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);
376if (db) fprintf(stderr, "out avahi_advertise\n");
377}
378
379void avahi_reset(void) {
380	int i;
381if (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) {
393if (db) fprintf(stderr, "    avahi_reset client/group null\n");
394		return;
395	}
396	avahi_entry_group_reset(_group);
397	rfbLog("Avahi resetting group.\n");
398if (db) fprintf(stderr, "out avahi_reset\n");
399}
400
401static void avahi_timeout (int sig) {
402	rfbLog("sig: %d, avahi_cleanup timed out.\n", sig);
403	exit(1);
404}
405
406
407void avahi_cleanup(void) {
408if (db) fprintf(stderr, "in  avahi_cleanup\n");
409	if (!_client) {
410if (db) fprintf(stderr, "    avahi_cleanup client null\n");
411		return;
412	}
413if (db) fprintf(stderr, "    avahi_cleanup poll_lock\n");
414	avahi_threaded_poll_lock(_poll);
415if (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
423if (db) fprintf(stderr, "    avahi_cleanup client_free\n");
424	avahi_client_free(_client);
425	_client = NULL;
426
427if (db) fprintf(stderr, "    avahi_cleanup poll_free\n");
428	avahi_threaded_poll_free(_poll);
429	_poll = NULL;
430if (db) fprintf(stderr, "out avahi_cleanup\n");
431}
432
433#endif
434
435