#include <signal.h>
#include <errno.h>
#include <string.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/stat.h>
#include <libdaemon/dfork.h>
#include <libdaemon/dsignal.h>
DAEMON_RUN,
DAEMON_KILL,
DAEMON_VERSION,
- DAEMON_HELP
+ DAEMON_HELP,
+ DAEMON_RELOAD
} DaemonCommand;
typedef struct {
gboolean daemonize;
gchar *config_file;
gboolean enable_dbus;
+ gboolean drop_root;
+ gboolean publish_resolv_conf;
+ gchar ** publish_dns_servers;
} DaemonConfig;
+#define RESOLV_CONF "/etc/resolv.conf"
+
+static AvahiEntryGroup *dns_servers_entry_group = NULL;
+static AvahiEntryGroup *resolv_conf_entry_group = NULL;
+
+static gchar **resolv_conf = NULL;
+
+static DaemonConfig config;
+
+#define MAX_NAME_SERVERS 10
+
+static gint load_resolv_conf(const DaemonConfig *config) {
+ gint ret = -1;
+ FILE *f;
+ gint i = 0;
+
+ g_strfreev(resolv_conf);
+ resolv_conf = NULL;
+
+ if (!config->publish_resolv_conf)
+ return 0;
+
+ if (!(f = fopen(RESOLV_CONF, "r"))) {
+ avahi_log_warn("Failed to open "RESOLV_CONF".");
+ goto finish;
+ }
+
+ resolv_conf = g_new0(gchar*, MAX_NAME_SERVERS+1);
+
+ while (!feof(f) && i < MAX_NAME_SERVERS) {
+ char ln[128];
+ gchar *p;
+
+ if (!(fgets(ln, sizeof(ln), f)))
+ break;
+
+ ln[strcspn(ln, "\r\n#")] = 0;
+ p = ln + strspn(ln, "\t ");
+
+ if (g_str_has_prefix(p, "nameserver")) {
+ p += 10;
+ p += strspn(p, "\t ");
+ p[strcspn(p, "\t ")] = 0;
+ resolv_conf[i++] = strdup(p);
+ }
+ }
+
+ ret = 0;
+
+finish:
+
+ if (ret != 0) {
+ g_strfreev(resolv_conf);
+ resolv_conf = NULL;
+ }
+
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+static AvahiEntryGroup* add_dns_servers(AvahiServer *s, gchar **l) {
+ gchar **p;
+ AvahiEntryGroup *g;
+
+ g_assert(s);
+ g_assert(l);
+
+ g = avahi_entry_group_new(s, NULL, NULL);
+
+ for (p = l; *p; p++) {
+ AvahiAddress a;
+
+ if (!avahi_address_parse(*p, AF_UNSPEC, &a))
+ avahi_log_warn("Failed to parse address '%s', ignoring.", *p);
+ else
+ if (avahi_server_add_dns_server_address(s, g, -1, AF_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, &a, 53) < 0) {
+ avahi_entry_group_free(g);
+ return NULL;
+ }
+ }
+
+ avahi_entry_group_commit(g);
+
+ return g;
+
+}
+
+static void remove_dns_server_entry_groups(void) {
+ if (resolv_conf_entry_group) {
+ avahi_entry_group_free(resolv_conf_entry_group);
+ resolv_conf_entry_group = NULL;
+ }
+
+ if (dns_servers_entry_group) {
+ avahi_entry_group_free(dns_servers_entry_group);
+ dns_servers_entry_group = NULL;
+ }
+}
+
static void server_callback(AvahiServer *s, AvahiServerState state, gpointer userdata) {
+ DaemonConfig *config = userdata;
+
g_assert(s);
+ g_assert(config);
if (state == AVAHI_SERVER_RUNNING) {
avahi_log_info("Server startup complete. Host name is <%s>", avahi_server_get_host_name_fqdn(s));
static_service_add_to_server();
+
+ remove_dns_server_entry_groups();
+
+ if (resolv_conf && resolv_conf[0])
+ resolv_conf_entry_group = add_dns_servers(s, resolv_conf);
+
+ if (config->publish_dns_servers && config->publish_dns_servers[0])
+ dns_servers_entry_group = add_dns_servers(s, config->publish_dns_servers);
+
} else if (state == AVAHI_SERVER_COLLISION) {
gchar *n;
static_service_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);
avahi_server_set_host_name(s, n);
" -h --help Show this help\n"
" -D --daemonize Daemonize after startup\n"
" -k --kill Kill a running daemon\n"
+ " -r --reload Request a running daemon to reload static services\n"
" -V --version Show version\n"
- " -f --file=FILE Load the specified configuration file instead of the default\n",
+ " -f --file=FILE Load the specified configuration file instead of\n"
+ " "AVAHI_CONFIG_FILE"\n",
argv0);
}
{ "kill", no_argument, NULL, 'k' },
{ "version", no_argument, NULL, 'V' },
{ "file", required_argument, NULL, 'f' },
+ { "reload", no_argument, NULL, 'r' },
};
g_assert(config);
opterr = 0;
- while ((c = getopt_long(argc, argv, "hDkVf:", long_options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hDkVf:r", long_options, NULL)) >= 0) {
switch(c) {
case 'h':
g_free(config->config_file);
config->config_file = g_strdup(optarg);
break;
+ case 'r':
+ config->command = DAEMON_RELOAD;
+ break;
default:
fprintf(stderr, "Invalid command line argument: %c\n", c);
return -1;
g_assert(config);
f = g_key_file_new();
+ g_key_file_set_list_separator(f, ',');
if (!g_key_file_load_from_file(f, config->config_file ? config->config_file : AVAHI_CONFIG_FILE, G_KEY_FILE_NONE, &err)) {
fprintf(stderr, "Unable to read config file: %s\n", err->message);
config->server_config.use_iff_running = is_yes(v);
else if (g_strcasecmp(*k, "enable-dbus") == 0)
config->enable_dbus = is_yes(v);
+ else if (g_strcasecmp(*k, "drop-root") == 0)
+ config->drop_root = is_yes(v);
else {
fprintf(stderr, "Invalid configuration key \"%s\" in group \"%s\"\n", *k, *g);
goto finish;
config->server_config.publish_workstation = is_yes(v);
else if (g_strcasecmp(*k, "publish-domain") == 0)
config->server_config.publish_domain = is_yes(v);
- else {
+ else if (g_strcasecmp(*k, "publish-resolv-conf-dns-servers") == 0)
+ config->publish_resolv_conf = is_yes(v);
+ else if (g_strcasecmp(*k, "publish-dns-servers") == 0) {
+ g_strfreev(config->publish_dns_servers);
+ config->publish_dns_servers = g_key_file_get_string_list(f, *g, *k, NULL, NULL);
+ } else {
fprintf(stderr, "Invalid configuration key \"%s\" in group \"%s\"\n", *k, *g);
goto finish;
}
static gboolean signal_callback(GIOChannel *source, GIOCondition condition, gpointer data) {
gint sig;
+ GMainLoop *loop = data;
+
g_assert(source);
+ g_assert(loop);
if ((sig = daemon_signal_next()) <= 0) {
avahi_log_error("daemon_signal_next() failed");
"Got %s, quitting.",
sig == SIGINT ? "SIGINT" :
(sig == SIGQUIT ? "SIGQUIT" : "SIGTERM"));
- g_main_loop_quit((GMainLoop*) data);
+ g_main_loop_quit(loop);
break;
case SIGHUP:
avahi_log_info("Got SIGHUP, reloading.");
static_service_load();
+ static_service_add_to_server();
+
+ if (resolv_conf_entry_group) {
+ avahi_entry_group_free(resolv_conf_entry_group);
+ resolv_conf_entry_group = NULL;
+ }
+
+ load_resolv_conf(&config);
+
+ if (resolv_conf && resolv_conf[0])
+ resolv_conf_entry_group = add_dns_servers(avahi_server, resolv_conf);
+
break;
default:
goto finish;
#endif
- if (!(avahi_server = avahi_server_new(NULL, &config->server_config, server_callback, NULL)))
+ if (!(avahi_server = avahi_server_new(NULL, &config->server_config, server_callback, config)))
goto finish;
+
+ load_resolv_conf(config);
static_service_load();
static_service_remove_from_server();
static_service_free_all();
+ remove_dns_server_entry_groups();
simple_protocol_shutdown();
dbus_protocol_shutdown();
#endif
+
if (avahi_server)
avahi_server_free(avahi_server);
return r;
}
+static gint drop_root(void) {
+ struct passwd *pw;
+ struct group * gr;
+ gint r;
+
+ if (!(pw = getpwnam(AVAHI_USER))) {
+ avahi_log_error( "Failed to find user '"AVAHI_USER"'.");
+ return -1;
+ }
+
+ if (!(gr = getgrnam(AVAHI_GROUP))) {
+ avahi_log_error( "Failed to find group '"AVAHI_GROUP"'.");
+ return -1;
+ }
+
+ 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);
+
+ if (initgroups(AVAHI_USER, gr->gr_gid) != 0) {
+ avahi_log_error("Failed to change group list: %s", strerror(errno));
+ return -1;
+ }
+
+#if defined(HAVE_SETRESGID)
+ r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid);
+#elif defined(HAVE_SETREGID)
+ r = setregid(gr->gr_gid, gr->gr_gid);
+#else
+ if ((r = setgid(gr->gr_gid)) >= 0)
+ r = setegid(gr->gr_gid);
+#endif
+
+ if (r < 0) {
+ avahi_log_error("Failed to change GID: %s", strerror(errno));
+ return -1;
+ }
+
+#if defined(HAVE_SETRESUID)
+ r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid);
+#elif defined(HAVE_SETREUID)
+ r = setreuid(pw->pw_uid, pw->pw_uid);
+#else
+ if ((r = setuid(pw->pw_uid)) >= 0)
+ r = seteuid(pw->pw_uid);
+#endif
+
+ if (r < 0) {
+ avahi_log_error("Failed to change UID: %s", strerror(errno));
+ return -1;
+ }
+
+ g_setenv("USER", pw->pw_name, 1);
+ g_setenv("LOGNAME", pw->pw_name, 1);
+ g_setenv("HOME", pw->pw_dir, 1);
+
+ avahi_log_info("Successfully dropped root privileges.");
+
+ return 0;
+}
+
+static const char* pid_file_proc(void) {
+ return AVAHI_RUNTIME_DIR"/avahi-daemon.pid";
+}
+
+static gint make_runtime_dir(void) {
+ gint r = -1;
+ mode_t u;
+ gboolean reset_umask = FALSE;
+ struct passwd *pw;
+ struct group * gr;
+ struct stat st;
+
+ if (!(pw = getpwnam(AVAHI_USER))) {
+ avahi_log_error( "Failed to find user '"AVAHI_USER"'.");
+ goto fail;
+ }
+
+ if (!(gr = getgrnam(AVAHI_GROUP))) {
+ avahi_log_error( "Failed to find group '"AVAHI_GROUP"'.");
+ goto fail;
+ }
+
+ u = umask(0000);
+ reset_umask = TRUE;
+
+ if (mkdir(AVAHI_RUNTIME_DIR, 0755) < 0 && errno != EEXIST) {
+ avahi_log_error("mkdir(\""AVAHI_RUNTIME_DIR"\"): %s", strerror(errno));
+ goto fail;
+ }
+
+ chown(AVAHI_RUNTIME_DIR, pw->pw_uid, gr->gr_gid);
+
+ if (stat(AVAHI_RUNTIME_DIR, &st) < 0) {
+ avahi_log_error("stat(): %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode) || st.st_uid != pw->pw_uid || st.st_gid != gr->gr_gid) {
+ avahi_log_error("Failed to create runtime directory "AVAHI_RUNTIME_DIR".");
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+ if (reset_umask)
+ umask(u);
+ return r;
+
+}
+
int main(int argc, char *argv[]) {
gint r = 255;
- DaemonConfig config;
const gchar *argv0;
gboolean wrote_pid_file = FALSE;
config.daemonize = FALSE;
config.config_file = NULL;
config.enable_dbus = TRUE;
+ config.drop_root = TRUE;
+ config.publish_dns_servers = NULL;
+ config.publish_resolv_conf = FALSE;
if ((argv0 = strrchr(argv[0], '/')))
argv0++;
argv0 = argv[0];
daemon_pid_file_ident = daemon_log_ident = (char *) argv0;
+ daemon_pid_file_proc = pid_file_proc;
if (parse_command_line(&config, argc, argv) < 0)
goto finish;
- if (load_config_file(&config) < 0)
- goto finish;
-
if (config.command == DAEMON_HELP) {
help(stdout, argv0);
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));
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_RUN) {
pid_t pid;
+
+ if (getuid() != 0) {
+ 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)
/* Child */
}
+ chdir("/");
+
+ if (make_runtime_dir() < 0)
+ goto finish;
+
+ if (config.drop_root) {
+ if (drop_root() < 0)
+ goto finish;
+ }
+
if (daemon_pid_file_create() < 0) {
avahi_log_error("Failed to create PID file: %s", strerror(errno));
avahi_server_config_free(&config.server_config);
g_free(config.config_file);
+ g_strfreev(config.publish_dns_servers);
+ g_strfreev(resolv_conf);
if (wrote_pid_file)
daemon_pid_file_remove();