]> git.meshlink.io Git - catta/blob - avahi-dnsconfd/main.c
get rid of a lot of old svn cruft
[catta] / avahi-dnsconfd / main.c
1 /***
2   This file is part of avahi.
3
4   avahi is free software; you can redistribute it and/or modify it
5   under the terms of the GNU Lesser General Public License as
6   published by the Free Software Foundation; either version 2.1 of the
7   License, or (at your option) any later version.
8
9   avahi is distributed in the hope that it will be useful, but WITHOUT
10   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12   Public License for more details.
13
14   You should have received a copy of the GNU Lesser General Public
15   License along with avahi; if not, write to the Free Software
16   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17   USA.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/select.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <sys/ioctl.h>
33 #include <net/if.h>
34 #include <signal.h>
35 #include <sys/wait.h>
36 #include <getopt.h>
37 #include <assert.h>
38 #include <inttypes.h>
39 #include <stdlib.h>
40
41 #include <avahi-common/llist.h>
42 #include <avahi-common/malloc.h>
43 #include <avahi-common/address.h>
44
45 #include <libdaemon/dfork.h>
46 #include <libdaemon/dsignal.h>
47 #include <libdaemon/dlog.h>
48 #include <libdaemon/dpid.h>
49 #include <libdaemon/dexec.h>
50
51 #define BROWSE_DNS_SERVERS "BROWSE-DNS-SERVERS\n"
52
53 #define ENV_INTERFACE_DNS_SERVERS  "AVAHI_INTERFACE_DNS_SERVERS"
54 #define ENV_DNS_SERVERS "AVAHI_DNS_SERVERS"
55 #define ENV_INTERFACE "AVAHI_INTERFACE"
56
57 static enum {
58     ACKWAIT,
59     BROWSING
60 } state = ACKWAIT;
61
62 static enum {
63     DAEMON_RUN,
64     DAEMON_KILL,
65     DAEMON_REFRESH,
66     DAEMON_VERSION,
67     DAEMON_HELP,
68     DAEMON_CHECK
69 } command = DAEMON_RUN;
70
71 static int quit = 0;
72 static int daemonize = 0;
73
74 #if !HAVE_DECL_ENVIRON
75 extern char **environ;
76 #endif
77
78 typedef struct DNSServerInfo DNSServerInfo;
79
80 struct DNSServerInfo {
81     AvahiIfIndex interface;
82     AvahiProtocol protocol;
83     char *address;
84     AVAHI_LLIST_FIELDS(DNSServerInfo, servers);
85 };
86
87 static AVAHI_LLIST_HEAD(DNSServerInfo, servers);
88
89 static void server_info_free(DNSServerInfo *i) {
90     assert(i);
91
92     avahi_free(i->address);
93
94     AVAHI_LLIST_REMOVE(DNSServerInfo, servers, servers, i);
95     avahi_free(i);
96 }
97
98 static DNSServerInfo* get_server_info(AvahiIfIndex interface, AvahiProtocol protocol, const char *address) {
99     DNSServerInfo *i;
100     assert(address);
101
102     for (i = servers; i; i = i->servers_next)
103         if (i->interface == interface &&
104             i->protocol == protocol &&
105             strcmp(i->address, address) == 0)
106             return i;
107
108     return NULL;
109 }
110
111 static DNSServerInfo* new_server_info(AvahiIfIndex interface, AvahiProtocol protocol, const char *address) {
112     DNSServerInfo *i;
113
114     assert(address);
115
116     i = avahi_new(DNSServerInfo, 1);
117     i->interface = interface;
118     i->protocol = protocol;
119     i->address = avahi_strdup(address);
120
121     AVAHI_LLIST_PREPEND(DNSServerInfo, servers, servers, i);
122
123     return i;
124 }
125
126 static int set_cloexec(int fd) {
127     int n;
128
129     assert(fd >= 0);
130
131     if ((n = fcntl(fd, F_GETFD)) < 0)
132         return -1;
133
134     if (n & FD_CLOEXEC)
135         return 0;
136
137     return fcntl(fd, F_SETFD, n|FD_CLOEXEC);
138 }
139
140 static int open_socket(void) {
141     int fd = -1;
142     struct sockaddr_un sa;
143
144     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
145         daemon_log(LOG_ERR, "socket(): %s", strerror(errno));
146         goto fail;
147     }
148
149     if (set_cloexec(fd) < 0) {
150         daemon_log(LOG_ERR, "fcntl(): %s", strerror(errno));
151         goto fail;
152     }
153
154     memset(&sa, 0, sizeof(sa));
155     sa.sun_family = AF_UNIX;
156     strncpy(sa.sun_path, AVAHI_SOCKET, sizeof(sa.sun_path)-1);
157     sa.sun_path[sizeof(sa.sun_path)-1] = 0;
158
159     if (connect(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
160         daemon_log(LOG_ERR, "connect(): %s", strerror(errno));
161         daemon_log(LOG_INFO, "Failed to connect to the daemon. This probably means that you");
162         daemon_log(LOG_INFO, "didn't start avahi-daemon before avahi-dnsconfd.");
163         goto fail;
164     }
165
166     return fd;
167
168 fail:
169     if (fd >= 0)
170         close(fd);
171
172     return -1;
173 }
174
175 static ssize_t loop_write(int fd, const void*data, size_t size) {
176
177     ssize_t ret = 0;
178     assert(fd >= 0 && data && size);
179
180     while (size > 0) {
181         ssize_t r;
182
183         if ((r = write(fd, data, size)) < 0)
184             return r;
185
186         if (r == 0)
187             break;
188
189         ret += r;
190         data = (const uint8_t*) data + r;
191         size -= r;
192     }
193
194     return ret;
195 }
196
197 static char *concat_dns_servers(AvahiIfIndex interface) {
198     DNSServerInfo *i;
199     char *r = NULL;
200
201     for (i = servers; i; i = i->servers_next)
202         if (i->interface == interface || interface <= 0) {
203             DNSServerInfo *j;
204             char *t;
205
206             /* Filter out double entries */
207             for (j = servers; j != i; j = j->servers_next)
208                 if (j->interface == interface || interface <= 0)
209                     if (strcmp(i->address, j->address) == 0)
210                         break;
211
212             if (j != i)
213                 continue;
214
215             if (!r)
216                 t = avahi_strdup(i->address);
217             else
218                 t = avahi_strdup_printf("%s %s", r, i->address);
219
220             avahi_free(r);
221             r = t;
222         }
223
224     return r;
225 }
226
227 static void set_env(const char *name, const char *value) {
228     char **e;
229     size_t l;
230
231     assert(name);
232     assert(value);
233
234     l = strlen(name);
235
236     for (e = environ; *e; e++) {
237         /* Search for the variable */
238         if (strlen(*e) < l+1)
239             continue;
240
241         if (strncmp(*e, name, l) != 0 || (*e)[l] != '=')
242             continue;
243
244         /* We simply free the record, sicne we know that we created it previously */
245         avahi_free(*e);
246         *e = avahi_strdup_printf("%s=%s", name, value);
247         return;
248     }
249
250     assert(0);
251 }
252
253 static void run_script(int new, AvahiIfIndex interface, AvahiProtocol protocol, const char *address) {
254     char *p;
255     int ret;
256     char ia[16], pa[16];
257     char name[IF_NAMESIZE];
258
259     assert(interface > 0);
260
261     if (!if_indextoname(interface, name))
262         return;
263
264     p = concat_dns_servers(interface);
265     set_env(ENV_INTERFACE_DNS_SERVERS, p ? p : "");
266     avahi_free(p);
267
268     p = concat_dns_servers(-1);
269     set_env(ENV_DNS_SERVERS, p ? p : "");
270     avahi_free(p);
271
272     set_env(ENV_INTERFACE, name);
273
274     snprintf(ia, sizeof(ia), "%i", (int) interface);
275     snprintf(pa, sizeof(pa), "%i", (int) protocol);
276
277     if (daemon_exec("/", &ret, AVAHI_DNSCONF_SCRIPT, AVAHI_DNSCONF_SCRIPT, new ? "+" : "-", address, ia, pa, avahi_proto_to_string(protocol), NULL) < 0)
278         daemon_log(LOG_WARNING, "Failed to run script");
279     else if (ret != 0)
280         daemon_log(LOG_WARNING, "Script returned with non-zero exit code %i", ret);
281 }
282
283 static int new_line(const char *l) {
284     assert(l);
285
286     if (state == ACKWAIT) {
287         if (*l != '+') {
288             daemon_log(LOG_ERR, "Avahi command failed: %s", l);
289             return -1;
290         }
291
292         daemon_log(LOG_INFO, "Successfully connected to Avahi daemon.");
293         state = BROWSING;
294     } else {
295         AvahiIfIndex interface;
296         AvahiProtocol protocol;
297         int i_interface, i_protocol, port;
298         char a[AVAHI_ADDRESS_STR_MAX];
299
300         assert(state == BROWSING);
301
302         if (*l != '<' && *l != '>') {
303             daemon_log(LOG_ERR, "Avahi sent us an invalid browsing line: %s", l);
304             return -1;
305         }
306
307         if (sscanf(l+1, "%i %i %39s %i", &i_interface, &i_protocol, a, &port) != 4) {
308             daemon_log(LOG_ERR, "Failed to parse browsing line: %s", l);
309             return -1;
310         }
311
312         interface = (AvahiIfIndex) i_interface;
313         protocol = (AvahiProtocol) i_protocol;
314
315         if (*l == '>') {
316             if (port != 53)
317                 daemon_log(LOG_WARNING, "DNS server with port address != 53 found, ignoring");
318             else {
319                 daemon_log(LOG_INFO, "New DNS Server %s (interface: %i.%s)", a, interface, avahi_proto_to_string(protocol));
320                 new_server_info(interface, protocol, a);
321                 run_script(1, interface, protocol, a);
322             }
323         } else {
324             DNSServerInfo *i;
325
326             if (port == 53)
327                 if ((i = get_server_info(interface, protocol, a))) {
328                     daemon_log(LOG_INFO, "DNS Server %s removed (interface: %i.%s)", a, interface, avahi_proto_to_string(protocol));
329                     server_info_free(i);
330                     run_script(0, interface, protocol, a);
331                 }
332         }
333
334     }
335
336     return 0;
337 }
338
339 static int do_connect(void) {
340     int fd = -1;
341
342     if ((fd = open_socket()) < 0)
343         goto fail;
344
345     if (loop_write(fd, BROWSE_DNS_SERVERS, sizeof(BROWSE_DNS_SERVERS)-1) < 0) {
346         daemon_log(LOG_ERR, "write(): %s", strerror(errno));
347         goto fail;
348     }
349
350     state = ACKWAIT;
351     return fd;
352
353 fail:
354     if (fd >= 0)
355         close(fd);
356
357     return -1;
358 }
359
360 static void free_dns_server_info_list(void) {
361     while (servers) {
362         AvahiIfIndex interface = servers->interface;
363         AvahiProtocol protocol = servers->protocol;
364         char *address = avahi_strdup(servers->address);
365         server_info_free(servers);
366
367         run_script(0, interface, protocol, address);
368         avahi_free(address);
369     }
370 }
371
372 static void help(FILE *f, const char *argv0) {
373     fprintf(f,
374             "%s [options]\n"
375             "    -h --help        Show this help\n"
376             "    -D --daemonize   Daemonize after startup\n"
377             "    -k --kill        Kill a running daemon\n"
378             "    -r --refresh     Request a running daemon to refresh DNS server data\n"
379             "    -c --check       Return 0 if a daemon is already running\n"
380             "    -V --version     Show version\n",
381             argv0);
382 }
383
384 static int parse_command_line(int argc, char *argv[]) {
385     int c;
386
387     static const struct option long_options[] = {
388         { "help",      no_argument,       NULL, 'h' },
389         { "daemonize", no_argument,       NULL, 'D' },
390         { "kill",      no_argument,       NULL, 'k' },
391         { "version",   no_argument,       NULL, 'V' },
392         { "refresh",   no_argument,       NULL, 'r' },
393         { "check",     no_argument,       NULL, 'c' },
394         { NULL, 0, NULL, 0 }
395     };
396
397     while ((c = getopt_long(argc, argv, "hDkVrc", long_options, NULL)) >= 0) {
398
399         switch(c) {
400             case 'h':
401                 command = DAEMON_HELP;
402                 break;
403             case 'D':
404                 daemonize = 1;
405                 break;
406             case 'k':
407                 command = DAEMON_KILL;
408                 break;
409             case 'V':
410                 command = DAEMON_VERSION;
411                 break;
412             case 'r':
413                 command = DAEMON_REFRESH;
414                 break;
415             case 'c':
416                 command = DAEMON_CHECK;
417                 break;
418             default:
419                 return -1;
420         }
421     }
422
423     if (optind < argc) {
424         fprintf(stderr, "Too many arguments\n");
425         return -1;
426     }
427
428     return 0;
429 }
430
431 static int run_daemon(void) {
432     int fd = -1, ret = -1;
433     char buf[1024];
434     size_t buflen = 0;
435
436     AVAHI_LLIST_HEAD_INIT(DNSServerInfo, servers);
437
438     daemon_signal_init(SIGINT, SIGTERM, SIGCHLD, SIGHUP, 0);
439
440     /* Allocate some memory for our environment variables */
441     putenv(avahi_strdup(ENV_INTERFACE"="));
442     putenv(avahi_strdup(ENV_DNS_SERVERS"="));
443     putenv(avahi_strdup(ENV_INTERFACE_DNS_SERVERS"="));
444
445     if ((fd = do_connect()) < 0)
446         goto finish;
447
448     if (daemonize)
449         daemon_retval_send(0);
450
451     ret = 0;
452
453     while (!quit) {
454         fd_set rfds, wfds;
455
456         FD_ZERO(&rfds);
457         FD_ZERO(&wfds);
458
459         FD_SET(fd, &rfds);
460         FD_SET(daemon_signal_fd(), &rfds);
461
462         for (;;) {
463             if (select(fd+1, &rfds, NULL, NULL, NULL) < 0) {
464                 if (errno == EINTR)
465                     continue;
466
467                 daemon_log(LOG_ERR, "select(): %s", strerror(errno));
468                 goto finish;
469             }
470
471             break;
472         }
473
474         if (FD_ISSET(daemon_signal_fd(), &rfds)) {
475
476             int sig;
477
478             if ((sig = daemon_signal_next()) <= 0) {
479                 daemon_log(LOG_ERR, "daemon_signal_next() failed");
480                 goto finish;
481             }
482
483             switch(sig) {
484                 case SIGINT:
485                 case SIGTERM:
486                     daemon_log(LOG_INFO, "Got %s, quitting.", sig == SIGINT ? "SIGINT" : "SIGTERM");
487                     ret = 0;
488                     goto finish;
489
490                 case SIGCHLD:
491                     waitpid(-1, NULL, WNOHANG);
492                     break;
493
494                 case SIGHUP:
495                     daemon_log(LOG_INFO, "Refreshing DNS Server list");
496
497                     close(fd);
498                     free_dns_server_info_list();
499
500                     if ((fd = do_connect()) < 0)
501                         goto finish;
502
503                     break;
504             }
505
506         } else if (FD_ISSET(fd, &rfds)) {
507             ssize_t r;
508             char *n;
509
510             if ((r = read(fd, buf, sizeof(buf) - buflen - 1)) <= 0) {
511                 daemon_log(LOG_ERR, "read(): %s", r < 0 ? strerror(errno) : "EOF");
512                 goto finish;
513             }
514
515             buflen += r;
516             assert(buflen <= sizeof(buf)-1);
517
518             while ((n = memchr(buf, '\n', buflen))) {
519                 *(n++) = 0;
520
521                 if (new_line(buf) < 0)
522                     goto finish;
523
524                 buflen -= (n - buf);
525                 memmove(buf, n, buflen);
526             }
527
528             if (buflen >= sizeof(buf)-1) {
529                 /* The incoming line is horribly long */
530                 buf[sizeof(buf)-1] = 0;
531
532                 if (new_line(buf) < 0)
533                     goto finish;
534
535                 buflen = 0;
536             }
537         }
538     }
539
540 finish:
541
542     free_dns_server_info_list();
543
544     if (fd >= 0)
545         close(fd);
546
547     daemon_signal_done();
548
549     if (ret != 0 && daemonize)
550         daemon_retval_send(1);
551
552     return ret;
553 }
554
555 static const char* pid_file_proc(void) {
556     return AVAHI_RUNTIME_DIR"/avahi-dnsconfd.pid";
557 }
558
559 int main(int argc, char *argv[]) {
560     char *argv0;
561     int r = 1;
562     int wrote_pid_file = 0;
563
564     if ((argv0 = strrchr(argv[0], '/')))
565         argv0++;
566     else
567         argv0 = argv[0];
568
569     daemon_pid_file_ident = daemon_log_ident = argv0;
570     daemon_pid_file_proc = pid_file_proc;
571
572     if (parse_command_line(argc, argv) < 0)
573         goto finish;
574
575     if (command == DAEMON_RUN) {
576         pid_t pid;
577
578         if (getuid() != 0) {
579             daemon_log(LOG_ERR, "This program is intended to be run as root.");
580             goto finish;
581         }
582
583         if ((pid = daemon_pid_file_is_running()) >= 0) {
584             daemon_log(LOG_ERR, "Daemon already running on PID %u", pid);
585             goto finish;
586         }
587
588         if (daemonize) {
589             daemon_retval_init();
590
591             if ((pid = daemon_fork()) < 0)
592                 goto finish;
593             else if (pid != 0) {
594                 int ret;
595                 /** Parent **/
596
597                 if ((ret = daemon_retval_wait(20)) < 0) {
598                     daemon_log(LOG_ERR, "Could not receive return value from daemon process.");
599                     goto finish;
600                 }
601
602                 r = ret;
603                 goto finish;
604             }
605
606             /* Child */
607         }
608
609         chdir("/");
610
611         if (daemon_pid_file_create() < 0) {
612             daemon_log(LOG_ERR, "Failed to create PID file: %s", strerror(errno));
613
614             if (daemonize)
615                 daemon_retval_send(1);
616             goto finish;
617         } else
618             wrote_pid_file = 1;
619
620         if (run_daemon() < 0)
621             goto finish;
622
623         r = 0;
624     } else if (command == DAEMON_HELP) {
625         help(stdout, argv0);
626
627         r = 0;
628     } else if (command == DAEMON_VERSION) {
629         printf("%s "PACKAGE_VERSION"\n", argv0);
630
631         r = 0;
632     } else if (command == DAEMON_KILL) {
633         if (daemon_pid_file_kill_wait(SIGTERM, 5) < 0) {
634             daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno));
635             goto finish;
636         }
637
638         r = 0;
639     } else if (command == DAEMON_REFRESH) {
640         if (daemon_pid_file_kill(SIGHUP) < 0) {
641             daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno));
642             goto finish;
643         }
644
645         r = 0;
646     } else if (command == DAEMON_CHECK)
647         r = (daemon_pid_file_is_running() >= 0) ? 0 : 1;
648
649
650 finish:
651
652     if (daemonize)
653         daemon_retval_done();
654
655     if (wrote_pid_file)
656         daemon_pid_file_remove();
657
658     return r;
659 }