]> git.meshlink.io Git - catta/blob - avahi-daemon/main.c
ce3400a3dff2e69ecc5687f84bc9f975045e1e3d
[catta] / avahi-daemon / main.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10  
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <getopt.h>
27 #include <string.h>
28 #include <signal.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <grp.h>
33 #include <pwd.h>
34 #include <sys/stat.h>
35
36 #include <libdaemon/dfork.h>
37 #include <libdaemon/dsignal.h>
38 #include <libdaemon/dlog.h>
39 #include <libdaemon/dpid.h>
40
41 #include <avahi-core/core.h>
42 #include <avahi-core/log.h>
43
44 #include "main.h"
45 #include "simple-protocol.h"
46 #include "dbus-protocol.h"
47 #include "static-services.h"
48
49 AvahiServer *avahi_server = NULL;
50
51 typedef enum {
52     DAEMON_RUN,
53     DAEMON_KILL,
54     DAEMON_VERSION,
55     DAEMON_HELP,
56     DAEMON_RELOAD
57 } DaemonCommand;
58
59 typedef struct {
60     AvahiServerConfig server_config;
61     DaemonCommand command;
62     gboolean daemonize;
63     gchar *config_file;
64     gboolean enable_dbus;
65     gboolean drop_root;
66 } DaemonConfig;
67
68 static void server_callback(AvahiServer *s, AvahiServerState state, gpointer userdata) {
69     g_assert(s);
70
71     if (state == AVAHI_SERVER_RUNNING) {
72         avahi_log_info("Server startup complete.  Host name is <%s>", avahi_server_get_host_name_fqdn(s));
73         static_service_add_to_server();
74     } else if (state == AVAHI_SERVER_COLLISION) {
75         gchar *n;
76
77         static_service_remove_from_server();
78         
79         n = avahi_alternative_host_name(avahi_server_get_host_name(s));
80         avahi_log_warn("Host name conflict, retrying with <%s>", n);
81         avahi_server_set_host_name(s, n);
82         g_free(n);
83     }
84 }
85
86 static void help(FILE *f, const gchar *argv0) {
87     fprintf(f,
88             "%s [options]\n"
89             "    -h --help        Show this help\n"
90             "    -D --daemonize   Daemonize after startup\n"
91             "    -k --kill        Kill a running daemon\n"
92             "    -r --reload      Request a running daemon to reload static services\n"
93             "    -V --version     Show version\n"
94             "    -f --file=FILE   Load the specified configuration file instead of\n"
95             "                     "AVAHI_CONFIG_FILE"\n",
96             argv0);
97 }
98
99 static gint parse_command_line(DaemonConfig *config, int argc, char *argv[]) {
100     gint c;
101     
102     static const struct option const long_options[] = {
103         { "help",      no_argument,       NULL, 'h' },
104         { "daemonize", no_argument,       NULL, 'D' },
105         { "kill",      no_argument,       NULL, 'k' },
106         { "version",   no_argument,       NULL, 'V' },
107         { "file",      required_argument, NULL, 'f' },
108         { "reload",    no_argument,       NULL, 'r' },
109     };
110
111     g_assert(config);
112
113     opterr = 0;
114     while ((c = getopt_long(argc, argv, "hDkVf:r", long_options, NULL)) >= 0) {
115
116         switch(c) {
117             case 'h':
118                 config->command = DAEMON_HELP;
119                 break;
120             case 'D':
121                 config->daemonize = TRUE;
122                 break;
123             case 'k':
124                 config->command = DAEMON_KILL;
125                 break;
126             case 'V':
127                 config->command = DAEMON_VERSION;
128                 break;
129             case 'f':
130                 g_free(config->config_file);
131                 config->config_file = g_strdup(optarg);
132                 break;
133             case 'r':
134                 config->command = DAEMON_RELOAD;
135                 break;
136             default:
137                 fprintf(stderr, "Invalid command line argument: %c\n", c);
138                 return -1;
139         }
140     }
141
142     if (optind < argc) {
143         fprintf(stderr, "Too many arguments\n");
144         return -1;
145     }
146         
147     return 0;
148 }
149
150 static gboolean is_yes(const gchar *s) {
151     g_assert(s);
152     
153     return *s == 'y' || *s == 'Y';
154 }
155
156 static gint load_config_file(DaemonConfig *config) {
157     int r = -1;
158     GKeyFile *f = NULL;
159     GError *err = NULL;
160     gchar **groups = NULL, **g, **keys = NULL, *v = NULL;
161
162     g_assert(config);
163     
164     f = g_key_file_new();
165     
166     if (!g_key_file_load_from_file(f, config->config_file ? config->config_file : AVAHI_CONFIG_FILE, G_KEY_FILE_NONE, &err)) {
167         fprintf(stderr, "Unable to read config file: %s\n", err->message);
168         goto finish;
169     }
170
171     groups = g_key_file_get_groups(f, NULL);
172
173     for (g = groups; *g; g++) {
174         if (g_strcasecmp(*g, "server") == 0) {
175             gchar **k;
176             
177             keys = g_key_file_get_keys(f, *g, NULL, NULL);
178
179             for (k = keys; *k; k++) {
180
181                 v = g_key_file_get_value(f, *g, *k, NULL);
182                 
183                 if (g_strcasecmp(*k, "host-name") == 0) {
184                     g_free(config->server_config.host_name);
185                     config->server_config.host_name = v;
186                     v = NULL;
187                 } else if (g_strcasecmp(*k, "domain-name") == 0) {
188                     g_free(config->server_config.domain_name);
189                     config->server_config.domain_name = v;
190                     v = NULL;
191                 } else if (g_strcasecmp(*k, "use-ipv4") == 0)
192                     config->server_config.use_ipv4 = is_yes(v);
193                 else if (g_strcasecmp(*k, "use-ipv6") == 0)
194                     config->server_config.use_ipv6 = is_yes(v);
195                 else if (g_strcasecmp(*k, "check-response-ttl") == 0)
196                     config->server_config.check_response_ttl = is_yes(v);
197                 else if (g_strcasecmp(*k, "use-iff-running") == 0)
198                     config->server_config.use_iff_running = is_yes(v);
199                 else if (g_strcasecmp(*k, "enable-dbus") == 0)
200                     config->enable_dbus = is_yes(v);
201                 else if (g_strcasecmp(*k, "drop-root") == 0)
202                     config->drop_root = is_yes(v);
203                 else {
204                     fprintf(stderr, "Invalid configuration key \"%s\" in group \"%s\"\n", *k, *g);
205                     goto finish;
206                 }
207
208                 g_free(v);
209                 v = NULL;
210             }
211         
212             g_strfreev(keys);
213             keys = NULL;
214             
215         } else if (g_strcasecmp(*g, "publish") == 0) {
216             gchar **k;
217             
218             keys = g_key_file_get_keys(f, *g, NULL, NULL);
219
220             for (k = keys; *k; k++) {
221
222                 v = g_key_file_get_string(f, *g, *k, NULL);
223                 
224                 if (g_strcasecmp(*k, "publish-addresses") == 0)
225                     config->server_config.publish_addresses = is_yes(v);
226                 else if (g_strcasecmp(*k, "publish-hinfo") == 0)
227                     config->server_config.publish_hinfo = is_yes(v);
228                 else if (g_strcasecmp(*k, "publish-workstation") == 0)
229                     config->server_config.publish_workstation = is_yes(v);
230                 else if (g_strcasecmp(*k, "publish-domain") == 0)
231                     config->server_config.publish_domain = is_yes(v);
232                 else {
233                     fprintf(stderr, "Invalid configuration key \"%s\" in group \"%s\"\n", *k, *g);
234                     goto finish;
235                 }
236
237                 g_free(v);
238                 v = NULL;
239             }
240
241             g_strfreev(keys);
242             keys = NULL;
243
244         } else if (g_strcasecmp(*g, "reflector") == 0) {
245             gchar **k;
246             
247             keys = g_key_file_get_keys(f, *g, NULL, NULL);
248
249             for (k = keys; *k; k++) {
250
251                 v = g_key_file_get_string(f, *g, *k, NULL);
252                 
253                 if (g_strcasecmp(*k, "enable-reflector") == 0)
254                     config->server_config.enable_reflector = is_yes(v);
255                 else if (g_strcasecmp(*k, "reflect-ipv") == 0)
256                     config->server_config.reflect_ipv = is_yes(v);
257                 else {
258                     fprintf(stderr, "Invalid configuration key \"%s\" in group \"%s\"\n", *k, *g);
259                     goto finish;
260                 }
261
262                 g_free(v);
263                 v = NULL;
264             }
265     
266             g_strfreev(keys);
267             keys = NULL;
268             
269         } else {
270             fprintf(stderr, "Invalid configuration file group \"%s\".\n", *g);
271             goto finish;
272         }
273     }
274
275     r = 0;
276
277 finish:
278
279     g_strfreev(groups);
280     g_strfreev(keys);
281     g_free(v);
282
283     if (err)
284         g_error_free (err);
285
286     if (f)
287         g_key_file_free(f);
288     
289     return r;
290 }
291
292 static void log_function(AvahiLogLevel level, const gchar *txt) {
293
294     static const int const log_level_map[] = {
295         LOG_ERR,
296         LOG_WARNING,
297         LOG_NOTICE,
298         LOG_INFO,
299         LOG_DEBUG
300     };
301     
302     g_assert(level < AVAHI_LOG_LEVEL_MAX);
303     g_assert(txt);
304
305     daemon_log(log_level_map[level], "%s", txt);
306 }
307
308 static gboolean signal_callback(GIOChannel *source, GIOCondition condition, gpointer data) {
309     gint sig;
310     g_assert(source);
311
312     if ((sig = daemon_signal_next()) <= 0) {
313         avahi_log_error("daemon_signal_next() failed");
314         return FALSE;
315     }
316
317     switch (sig) {
318         case SIGINT:
319         case SIGQUIT:
320         case SIGTERM:
321             avahi_log_info(
322                 "Got %s, quitting.",
323                 sig == SIGINT ? "SIGINT" :
324                 (sig == SIGQUIT ? "SIGQUIT" : "SIGTERM"));
325             g_main_loop_quit((GMainLoop*) data);
326             break;
327
328         case SIGHUP:
329             avahi_log_info("Got SIGHUP, reloading.");
330             static_service_load();
331             static_service_add_to_server();
332             break;
333
334         default:
335             avahi_log_warn("Got spurious signal, ignoring.");
336             break;
337     }
338
339     return TRUE;
340 }
341
342 static gint run_server(DaemonConfig *config) {
343     GMainLoop *loop = NULL;
344     gint r = -1;
345     GIOChannel *io = NULL;
346     guint watch_id = (guint) -1;
347
348     g_assert(config);
349     
350     loop = g_main_loop_new(NULL, FALSE);
351
352     if (daemon_signal_init(SIGINT, SIGQUIT, SIGHUP, SIGTERM, 0) < 0) {
353         avahi_log_error("Could not register signal handlers (%s).", strerror(errno));
354         goto finish;
355     }
356
357     if (!(io = g_io_channel_unix_new(daemon_signal_fd()))) {
358         avahi_log_error( "Failed to create signal io channel.");
359         goto finish;
360     }
361
362     g_io_channel_set_close_on_unref(io, FALSE);
363     g_io_add_watch(io, G_IO_IN, signal_callback, loop);
364     
365     if (simple_protocol_setup(NULL) < 0)
366         goto finish;
367     
368 #ifdef ENABLE_DBUS
369     if (config->enable_dbus)
370         if (dbus_protocol_setup(loop) < 0)
371             goto finish;
372 #endif
373     
374     if (!(avahi_server = avahi_server_new(NULL, &config->server_config, server_callback, NULL)))
375         goto finish;
376     
377     static_service_load();
378
379     if (config->daemonize) {
380         daemon_retval_send(0);
381         r = 0;
382     }
383
384     g_main_loop_run(loop);
385
386 finish:
387     
388     static_service_remove_from_server();
389     static_service_free_all();
390     
391     simple_protocol_shutdown();
392
393 #ifdef ENABLE_DBUS
394     if (config->enable_dbus)
395         dbus_protocol_shutdown();
396 #endif
397
398     if (avahi_server)
399         avahi_server_free(avahi_server);
400
401     daemon_signal_done();
402
403     if (watch_id != (guint) -1)
404         g_source_remove(watch_id);
405     
406     if (io)
407         g_io_channel_unref(io);
408
409         
410     if (loop)
411         g_main_loop_unref(loop);
412
413     if (r != 0 && config->daemonize)
414         daemon_retval_send(1);
415     
416     return r;
417 }
418
419 static gint drop_root(void) {
420     struct passwd *pw;
421     struct group * gr;
422     gint r;
423     
424     if (!(pw = getpwnam(AVAHI_USER))) {
425         avahi_log_error( "Failed to find user '"AVAHI_USER"'.");
426         return -1;
427     }
428
429     if (!(gr = getgrnam(AVAHI_GROUP))) {
430         avahi_log_error( "Failed to find group '"AVAHI_GROUP"'.");
431         return -1;
432     }
433
434     avahi_log_info("Found user '"AVAHI_USER"' (UID %lu) and group '"AVAHI_GROUP"' (GID %lu).", (unsigned long) pw->pw_uid, (unsigned long) gr->gr_gid);
435
436     if (initgroups(AVAHI_USER, gr->gr_gid) != 0) {
437         avahi_log_error("Failed to change group list: %s", strerror(errno));
438         return -1;
439     }
440
441 #if defined(HAVE_SETRESGID)
442     r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid);
443 #elif defined(HAVE_SETREGID)
444     r = setregid(gr->gr_gid, gr->gr_gid);
445 #else
446     if ((r = setgid(gr->gr_gid)) >= 0)
447         r = setegid(gr->gr_gid);
448 #endif
449
450     if (r < 0) {
451         avahi_log_error("Failed to change GID: %s", strerror(errno));
452         return -1;
453     }
454
455 #if defined(HAVE_SETRESUID)
456     r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid);
457 #elif defined(HAVE_SETREUID)
458     r = setreuid(pw->pw_uid, pw->pw_uid);
459 #else
460     if ((r = setuid(pw->pw_uid)) >= 0)
461         r = seteuid(pw->pw_uid);
462 #endif
463
464     if (r < 0) {
465         avahi_log_error("Failed to change UID: %s", strerror(errno));
466         return -1;
467     }
468
469     g_setenv("USER", pw->pw_name, 1);
470     g_setenv("LOGNAME", pw->pw_name, 1);
471     g_setenv("HOME", pw->pw_dir, 1);
472     
473     avahi_log_info("Successfully dropped root privileges.");
474
475     return 0;
476 }
477
478 static const char* pid_file_proc(void) {
479     return AVAHI_RUNTIME_DIR"/pid";
480 }
481
482 static gint make_runtime_dir(void) {
483     gint r = -1;
484     mode_t u;
485     gboolean reset_umask = FALSE;
486     struct passwd *pw;
487     struct group * gr;
488     struct stat st;
489
490     if (!(pw = getpwnam(AVAHI_USER))) {
491         avahi_log_error( "Failed to find user '"AVAHI_USER"'.");
492         goto fail;
493     }
494
495     if (!(gr = getgrnam(AVAHI_GROUP))) {
496         avahi_log_error( "Failed to find group '"AVAHI_GROUP"'.");
497         goto fail;
498     }
499
500     u = umask(0000);
501     reset_umask = TRUE;
502     
503     if (mkdir(AVAHI_RUNTIME_DIR, 0755) < 0 && errno != EEXIST) {
504         avahi_log_error("mkdir(\""AVAHI_RUNTIME_DIR"\"): %s", strerror(errno));
505         goto fail;
506     }
507     
508     chown(AVAHI_RUNTIME_DIR, pw->pw_uid, gr->gr_gid);
509
510     if (stat(AVAHI_RUNTIME_DIR, &st) < 0) {
511         avahi_log_error("stat(): %s\n", strerror(errno));
512         goto fail;
513     }
514
515     if (!S_ISDIR(st.st_mode) || st.st_uid != pw->pw_uid || st.st_gid != gr->gr_gid) {
516         avahi_log_error("Failed to create runtime directory "AVAHI_RUNTIME_DIR".");
517         goto fail;
518     }
519
520     r = 0;
521
522 fail:
523     if (reset_umask)
524         umask(u);
525     return r;
526
527 }
528     
529
530
531 int main(int argc, char *argv[]) {
532     gint r = 255;
533     DaemonConfig config;
534     const gchar *argv0;
535     gboolean wrote_pid_file = FALSE;
536
537     avahi_set_log_function(log_function);
538     
539     avahi_server_config_init(&config.server_config);
540     config.command = DAEMON_RUN;
541     config.daemonize = FALSE;
542     config.config_file = NULL;
543     config.enable_dbus = TRUE;
544     config.drop_root = TRUE;
545
546     if ((argv0 = strrchr(argv[0], '/')))
547         argv0++;
548     else
549         argv0 = argv[0];
550
551     daemon_pid_file_ident = daemon_log_ident = (char *) argv0;
552
553     daemon_pid_file_proc = pid_file_proc;
554     
555     if (parse_command_line(&config, argc, argv) < 0)
556         goto finish;
557
558     if (config.command == DAEMON_HELP) {
559         help(stdout, argv0);
560         r = 0;
561     } else if (config.command == DAEMON_VERSION) {
562         printf("%s "PACKAGE_VERSION"\n", argv0);
563
564     } else if (config.command == DAEMON_KILL) {
565         if (daemon_pid_file_kill_wait(SIGTERM, 5) < 0) {
566             avahi_log_warn("Failed to kill daemon: %s", strerror(errno));
567             goto finish;
568         }
569
570         r = 0;
571         
572     } else if (config.command == DAEMON_RELOAD) {
573         if (daemon_pid_file_kill(SIGHUP) < 0) {
574             avahi_log_warn("Failed to kill daemon: %s", strerror(errno));
575             goto finish;
576         }
577
578         r = 0;
579         
580     } else if (config.command == DAEMON_RUN) {
581         pid_t pid;
582
583         if (getuid() != 0) {
584             avahi_log_error("This program is intended to be run as root.");
585             goto finish;
586         }
587         
588         if ((pid = daemon_pid_file_is_running()) >= 0) {
589             avahi_log_error("Daemon already running on PID %u", pid);
590             goto finish;
591         }
592
593         if (load_config_file(&config) < 0)
594             goto finish;
595         
596         if (config.daemonize) {
597
598             daemon_retval_init();
599             
600             if ((pid = daemon_fork()) < 0)
601                 goto finish;
602             else if (pid != 0) {
603                 int ret;
604                 /** Parent **/
605
606                 if ((ret = daemon_retval_wait(20)) < 0) {
607                     avahi_log_error("Could not recieve return value from daemon process.");
608                     goto finish;
609                 }
610
611                 r = ret;
612                 goto finish;
613             }
614
615             /* Child */
616         }
617
618         chdir("/");
619
620         if (make_runtime_dir() < 0)
621             goto finish;
622
623         if (config.drop_root) {
624             if (drop_root() < 0)
625                 goto finish;
626         }
627
628         if (daemon_pid_file_create() < 0) {
629             avahi_log_error("Failed to create PID file: %s", strerror(errno));
630
631             if (config.daemonize)
632                 daemon_retval_send(1);
633             goto finish;
634         } else
635             wrote_pid_file = TRUE;
636
637         if (run_server(&config) == 0)
638             r = 0;
639     }
640         
641 finish:
642
643     if (config.daemonize)
644         daemon_retval_done();
645
646     avahi_server_config_free(&config.server_config);
647     g_free(config.config_file);
648
649     if (wrote_pid_file)
650         daemon_pid_file_remove();
651     
652     return r;
653 }