int main(int argc, char *argv[]) { int r = 255; int wrote_pid_file = 0; avahi_set_log_function(log_function); init_rand_seed(); avahi_server_config_init(&config.server_config); config.command = DAEMON_RUN; config.daemonize = 0; config.config_file = NULL; #ifdef HAVE_DBUS config.enable_dbus = 1; config.fail_on_missing_dbus = 1; config.n_clients_max = 0; config.n_objects_per_client_max = 0; config.n_entries_per_entry_group_max = 0; #endif config.drop_root = 1; config.set_rlimits = 1; #ifdef ENABLE_CHROOT config.use_chroot = 1; #endif config.modify_proc_title = 1; config.disable_user_service_publishing = 0; config.publish_dns_servers = NULL; config.publish_resolv_conf = 0; config.use_syslog = 0; config.debug = 0; config.rlimit_as_set = 0; config.rlimit_core_set = 0; config.rlimit_data_set = 0; config.rlimit_fsize_set = 0; config.rlimit_nofile_set = 0; config.rlimit_stack_set = 0; #ifdef RLIMIT_NPROC config.rlimit_nproc_set = 0; #endif if ((argv0 = strrchr(argv[0], '/'))) argv0 = avahi_strdup(argv0 + 1); else argv0 = avahi_strdup(argv[0]); daemon_pid_file_ident = (const char *) argv0; daemon_log_ident = (char*) argv0; daemon_pid_file_proc = pid_file_proc; if (parse_command_line(&config, argc, argv) < 0) goto finish; if (config.modify_proc_title) avahi_init_proc_title(argc, argv); #ifdef ENABLE_CHROOT config.use_chroot = config.use_chroot && config.drop_root; #endif if (config.command == DAEMON_HELP) { help(stdout); r = 0; } else if (config.command == DAEMON_VERSION) { printf("%s "PACKAGE_VERSION"\n", argv0); r = 0; } else if (config.command == DAEMON_KILL) { if (daemon_pid_file_kill_wait(SIGTERM, 5) < 0) { avahi_log_warn("Failed to kill daemon: %s", strerror(errno)); goto finish; } r = 0; } else if (config.command == DAEMON_RELOAD) { if (daemon_pid_file_kill(SIGHUP) < 0) { avahi_log_warn("Failed to kill daemon: %s", strerror(errno)); goto finish; } r = 0; } else if (config.command == DAEMON_CHECK) r = (daemon_pid_file_is_running() >= 0) ? 0 : 1; else if (config.command == DAEMON_RUN) { pid_t pid; if (getuid() != 0 && config.drop_root) { avahi_log_error("This program is intended to be run as root."); goto finish; } if ((pid = daemon_pid_file_is_running()) >= 0) { avahi_log_error("Daemon already running on PID %u", pid); goto finish; } if (load_config_file(&config) < 0) goto finish; if (config.daemonize) { daemon_retval_init(); if ((pid = daemon_fork()) < 0) goto finish; else if (pid != 0) { int ret; /** Parent **/ if ((ret = daemon_retval_wait(20)) < 0) { avahi_log_error("Could not receive return value from daemon process."); goto finish; } r = ret; goto finish; } /* Child */ } if (config.use_syslog || config.daemonize) daemon_log_use = DAEMON_LOG_SYSLOG; if (sd_listen_fds(0) <= 0) if (daemon_close_all(-1) < 0) avahi_log_warn("Failed to close all remaining file descriptors: %s", strerror(errno)); daemon_reset_sigs(-1); daemon_unblock_sigs(-1); if (make_runtime_dir() < 0) goto finish; if (config.drop_root) { #ifdef ENABLE_CHROOT if (config.use_chroot) if (avahi_caps_reduce() < 0) goto finish; #endif if (drop_root() < 0) goto finish; #ifdef ENABLE_CHROOT if (config.use_chroot) if (avahi_caps_reduce2() < 0) goto finish; #endif } if (daemon_pid_file_create() < 0) { avahi_log_error("Failed to create PID file: %s", strerror(errno)); if (config.daemonize) daemon_retval_send(1); goto finish; } else wrote_pid_file = 1; if (config.set_rlimits) enforce_rlimits(); chdir("/"); #ifdef ENABLE_CHROOT if (config.drop_root && config.use_chroot) if (avahi_chroot_helper_start(argv0) < 0) { avahi_log_error("failed to start chroot() helper daemon."); goto finish; } #endif avahi_log_info("%s "PACKAGE_VERSION" starting up.", argv0); sd_notifyf(0, "STATUS=%s "PACKAGE_VERSION" starting up.", argv0); avahi_set_proc_title(argv0, "%s: starting up", argv0); if (run_server(&config) == 0) r = 0; avahi_log_info("%s "PACKAGE_VERSION" exiting.", argv0); sd_notifyf(0, "STATUS=%s "PACKAGE_VERSION" exiting.", argv0); } finish: if (config.daemonize) daemon_retval_done(); avahi_server_config_free(&config.server_config); avahi_free(config.config_file); avahi_strfreev(config.publish_dns_servers); avahi_strfreev(resolv_conf_name_servers); avahi_strfreev(resolv_conf_search_domains); if (wrote_pid_file) { #ifdef ENABLE_CHROOT avahi_chroot_helper_unlink(pid_file_proc()); #else daemon_pid_file_remove(); #endif } #ifdef ENABLE_CHROOT avahi_chroot_helper_shutdown(); #endif avahi_free(argv0); return r; }
static void server_callback(AvahiServer *s, AvahiServerState state, void *userdata) { DaemonConfig *c = userdata; assert(s); assert(c); /* This function is possibly called before the global variable * avahi_server has been set, therefore we do it explicitly */ avahi_server = s; #ifdef HAVE_DBUS if (c->enable_dbus && state != AVAHI_SERVER_INVALID && state != AVAHI_SERVER_FAILURE) dbus_protocol_server_state_changed(state); #endif switch (state) { case AVAHI_SERVER_RUNNING: avahi_log_info("Server startup complete. Host name is %s. Local service cookie is %u.", avahi_server_get_host_name_fqdn(s), avahi_server_get_local_service_cookie(s)); sd_notifyf(0, "STATUS=Server startup complete. Host name is %s. Local service cookie is %u.", avahi_server_get_host_name_fqdn(s), avahi_server_get_local_service_cookie(s)); avahi_set_proc_title(argv0, "%s: running [%s]", argv0, avahi_server_get_host_name_fqdn(s)); static_service_add_to_server(); static_hosts_add_to_server(); remove_dns_server_entry_groups(); if (c->publish_resolv_conf && resolv_conf_name_servers && resolv_conf_name_servers[0]) resolv_conf_entry_group = add_dns_servers(s, resolv_conf_entry_group, resolv_conf_name_servers); if (c->publish_dns_servers && c->publish_dns_servers[0]) dns_servers_entry_group = add_dns_servers(s, dns_servers_entry_group, c->publish_dns_servers); simple_protocol_restart_queries(); break; case AVAHI_SERVER_COLLISION: { char *n; static_service_remove_from_server(); static_hosts_remove_from_server(); remove_dns_server_entry_groups(); n = avahi_alternative_host_name(avahi_server_get_host_name(s)); avahi_log_warn("Host name conflict, retrying with %s", n); sd_notifyf(0, "STATUS=Host name conflict, retrying with %s", n); avahi_set_proc_title(argv0, "%s: collision [%s]", argv0, n); avahi_server_set_host_name(s, n); avahi_free(n); break; } case AVAHI_SERVER_FAILURE: avahi_log_error("Server error: %s", avahi_strerror(avahi_server_errno(s))); sd_notifyf(0, "STATUS=Server error: %s", avahi_strerror(avahi_server_errno(s))); avahi_simple_poll_quit(simple_poll_api); break; case AVAHI_SERVER_REGISTERING: sd_notifyf(0, "STATUS=Registering host name %s", avahi_server_get_host_name_fqdn(s)); avahi_set_proc_title(argv0, "%s: registering [%s]", argv0, avahi_server_get_host_name_fqdn(s)); static_service_remove_from_server(); static_hosts_remove_from_server(); remove_dns_server_entry_groups(); break; case AVAHI_SERVER_INVALID: break; } }
/** * Return value is the process return value - zero on success or 1 on failure. * * FIXME: This function is too long and unclear whether some code paths return * an IPC packet (or whether they shouldn't). There are probably lots of latent * bugs. */ int worker(const char *mapping_script_file, int sock) { ssize_t siz; AvahiNatpmdIPCReq req; assert(mapping_script_file); assert(mapping_script_file[0] == '/'); mapping_script = mapping_script_file; if (avahi_natpm_drop_caps() != 0) return 1; avahi_set_cloexec(sock); /* XXX: Audit to ensure no extra sockets are passed to the child */ avahi_set_proc_title(argv0, "%s: iptables helper", argv0); daemon_log(LOG_DEBUG, "%s: Worker process running with pid %d", __FUNCTION__, getpid()); while ((siz = recv(sock, &req, sizeof(req), 0)) > 0) { pid_t pid; int status; sigset_t newsigs, oldsigs; if (siz != sizeof(req)) { daemon_log(LOG_NOTICE, "IPC request packet was too short"); continue; } /* Prevent a race where the signal gets delivered before we wait on it */ if (-1 == sigemptyset(&newsigs)) { daemon_log(LOG_ERR, "sigemptyset() failed: %s", strerror(errno)); return 1; } if (-1 == sigaddset(&newsigs, SIGCHLD)) { daemon_log(LOG_ERR, "sigaddset(..., SIGCHLD) failed: %s", strerror(errno)); return 1; } if (-1 == sigprocmask(SIG_BLOCK, &newsigs, &oldsigs)) { daemon_log(LOG_ERR, "sigprocmask(SIG_BLOCK, ...) failed: %s", strerror(errno)); return 1; } pid = fork(); if (pid == -1) { daemon_log(LOG_ERR, "%s: fork() failed: %s", __FUNCTION__, strerror(errno)); return 1; } if (pid == 0) { /* child */ int ret = 1; /* Child process' return value */ /* Let the child receive SIGCHLD if it wants to */ if (-1 == sigprocmask(SIG_UNBLOCK, &newsigs, NULL)) daemon_log(LOG_WARNING, "Child worker process failed to unblock signals: %s", strerror(errno)); switch(req.op) { case IPCREQ_OP_ADD: /*@fallthrough@*/ case IPCREQ_OP_REMOVE: ret = op_add_remove(&req); break; case IPCREQ_OP_PREPARE: /*@fallthrough@*/ case IPCREQ_OP_CLEANUP: ret = op_prepare_cleanup(&req); break; case IPCREQ_OP_CLEAR: ret = op_clear(&req); break; default: daemon_log(LOG_WARNING, "Received an IPC packet with bogus operation field"); ret = 1; } _exit(ret); /* The child process ends here. */ } { /* parent */ int signum; siginfo_t siginfo; const struct timespec waittime = { MAX_RUN_SECONDS, 0 }; struct sigaction oldhandler, ourhandler; pid_t waitedpid; memset(&ourhandler, '\0', sizeof(ourhandler)); ourhandler.sa_handler = sigchld_handler; /* Set signal handler for SIGCHLD */ if (-1 == sigaction(SIGCHLD, &ourhandler, &oldhandler)) { daemon_log(LOG_ERR, "Setting signal handler for SIGCHLD failed: %s", strerror(errno)); return 1; } /* XXX: Check for SIGHUP and SIGTERM too to stay interactive? */ while ((signum = sigtimedwait(&newsigs, &siginfo, &waittime)) == -1 && errno == EINTR) ; assert(signum == SIGCHLD || (signum == -1 && errno == EAGAIN)); /* Remove signal handler for SIGCHLD */ if (-1 == sigaction(SIGCHLD, &oldhandler, NULL)) { daemon_log(LOG_ERR, "Restoring old SIGCHLD handler failed: %s", strerror(errno)); return 1; } /* Restore old signal state */ if (-1 == sigprocmask(SIG_SETMASK, &oldsigs, NULL)) { /* Only fails if SIG_SETMASK is invalid. Never happens ;) */ daemon_log(LOG_WARNING, "Restoring signal state (sigprocmask(SIG_SETMASK, ...)) failed: %s", strerror(errno)); } if (signum == -1) { assert(errno == EAGAIN); /* errno could also be EINVAL if waittime is invalid, in which * case we're stuffed anyway */ daemon_log(LOG_ERR, "Child worker process took more than %ld seconds, gave up waiting", waittime.tv_sec); continue; } /* SIGCHLD arrived. */ while (((waitedpid = waitpid(pid, &status, WNOHANG)) == -1 && errno == EINTR) || waitedpid != pid) ; if (waitedpid == -1) { daemon_log(LOG_ERR, "%s: Error waiting for child process to finish: %s", __FUNCTION__, strerror(errno)); continue; } assert(waitedpid == pid); if (pid > 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0) { req.result = NATPMP_RESULT_SUCCESS; } else { if (pid == -1) { daemon_log(LOG_WARNING, "%s: waitpid failed: %s (probably timed out)", __FUNCTION__, strerror(errno)); } else { if (WIFEXITED(status)) daemon_log(LOG_ERR, "%s: Child exited with status %d", __FUNCTION__, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) daemon_log(LOG_ERR, "%s: Child exited due to signal %d", __FUNCTION__, WTERMSIG(status)); else daemon_log(LOG_ERR, "%s: Child exited, unknown cause", __FUNCTION__); } req.result = NATPMP_RESULT_NO_RESOURCES; } if (send(sock, &req, sizeof(req), 0) < (ssize_t)sizeof(req)) daemon_log(LOG_WARNING, "Failed sending IPC response"); } } if (siz == -1) { /* FIXME: EINTR / EAGAIN? */ daemon_log(LOG_ERR, "%s: Error receiving on IPC socket", __FUNCTION__); return 1; } else { assert(siz == 0); daemon_log(LOG_INFO, "%s: IPC socket closed, exiting.", __FUNCTION__); } return 0; }