1 /*
2 Copyright (C) 2002-2010 Karl J. Runge <runge@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
kill_avahi_pid(void)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
try_avahi_helper(char * name,char * host,uint16_t port)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)
avahi_initialise(void)146 void avahi_initialise(void) {
147 rfbLog("avahi_initialise: no Avahi support at buildtime.\n");
148 }
149
avahi_advertise(char * name,char * host,uint16_t port)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
avahi_reset(void)162 void avahi_reset(void) {
163 kill_avahi_pid();
164 rfbLog("avahi_reset: no Avahi support at buildtime.\n");
165 }
166
avahi_cleanup(void)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
avahi_initialise(void)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
_avahi_entry_group_callback(AvahiEntryGroup * g,AvahiEntryGroupState state,void * userdata)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
_avahi_create_services(char * name,char * host,uint16_t port)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
avahi_advertise(char * name,char * host,uint16_t port)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
avahi_reset(void)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
avahi_timeout(int sig)401 static void avahi_timeout (int sig) {
402 rfbLog("sig: %d, avahi_cleanup timed out.\n", sig);
403 exit(1);
404 }
405
406
avahi_cleanup(void)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