From 56b9485f26d6811e9afe9e45ccecd5bfeb33c1a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Jan 2006 01:00:17 +0000 Subject: add autoconf stuff git-svn-id: file:///home/lennart/svn/public/mod_dnssd/trunk@11 634eccf8-0006-0410-930e-e16565b0b7de --- src/Makefile.in | 39 +++ src/mod_dnssd.c | 777 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 816 insertions(+) create mode 100644 src/Makefile.in create mode 100644 src/mod_dnssd.c (limited to 'src') diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..c16ff43 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,39 @@ +# $Id$ + +APXS=@APXS@ +APACHECTL=@APACHECTL@ +LIBS=-Wl,"@LIBS@" @AVAHI_LIBS@ +CFLAGS=-Wc,"@CFLAGS@" @AVAHI_CFLAGS@ + +all: mod_dnssd.la + +mod_dnssd.la: mod_dnssd.c + $(APXS) -c $(CFLAGS) $(LIBS) mod_dnssd.c + +install: mod_dnssd.la + $(APXS) -i -a mod_dnssd.la + +clean: + rm -rf *.o *.so *.loT .deps/ *.la *.lo *.slo .libs/ + +reload: install restart + +start: + $(APACHECTL) start + +restart: + $(APACHECTL) restart + +stop: + $(APACHECTL) stop + +mostlyclean: clean +distclean: clean +maintainer-clean: clean + rm -f Makefile + +Makefile: Makefile.in + (cd .. && ../configure.status ) + + +.PHONY: all install clean reload start restart stop mostlyclean distclean maintainer-clean diff --git a/src/mod_dnssd.c b/src/mod_dnssd.c new file mode 100644 index 0000000..96206b9 --- /dev/null +++ b/src/mod_dnssd.c @@ -0,0 +1,777 @@ +/* $Id$ */ + +/*** + Copyright 2006 Lennart Poettering + + Licensed under the Apache License, Version 2.0 (the "License"); you + may not use this file except in compliance with the License. You + may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define MOD_DNSSD_USERDATA_KEY "mod-dnssd" + +struct runtime_data; + +struct service_data { + struct runtime_data *runtime; + apr_pool_t *pool; + + char *host_name; + uint16_t port; + char *location; + char *name; + apr_array_header_t *types; + int append_host_name; + char *chosen_name; + + AvahiEntryGroup *group; + + struct service_data *next; +}; + +struct runtime_data { + server_rec *main_server; + AvahiClient *client; + AvahiSimplePoll *simple_poll; + struct global_config_data *global_config_data; + apr_pool_t* pool; + struct service_data *services; +}; + +struct global_config_data { + int enabled; + int user_dir; + int vhost; +}; + +static int sigterm_pipe_fds[2] = { -1, -1 }; + +module AP_MODULE_DECLARE_DATA dnssd_module; + +#define GET_CONFIG_DATA(s) ap_get_module_config((s)->module_config, &dnssd_module) + +static int set_nonblock(int fd) { + int n; + + ap_assert(fd >= 0); + + if ((n = fcntl(fd, F_GETFL)) < 0) + return -1; + + if (n & O_NONBLOCK) + return 0; + + return fcntl(fd, F_SETFL, n|O_NONBLOCK); +} + +static void add_service(struct runtime_data *r, const char *host_name, uint16_t port, const char *location, const char *name, const char *types, int append_host_name) { + struct service_data *d; + char *w; + ap_assert(r); + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "add_service: %s %s %s", host_name, location, name); */ + + d = apr_palloc(r->pool, sizeof(struct service_data)); + ap_assert(d); + + d->pool = NULL; + d->runtime = r; + d->host_name = apr_pstrdup(r->pool, host_name); + d->port = port; + d->location = apr_pstrdup(r->pool, location); + d->name = apr_pstrdup(r->pool, name); + d->append_host_name = append_host_name; + d->chosen_name = NULL; + + d->types = apr_array_make(r->pool, 4, sizeof(char*)); + + if (types) + while (*(w = ap_getword_conf(r->pool, &types)) != 0) + *(char**) apr_array_push(d->types) = w; + + d->group = NULL; + + d->next = r->services; + r->services = d; + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "done"); */ + +} + +static void assemble_services(struct runtime_data *r) { + ap_directive_t *v; + const char *default_host_name = NULL; + uint16_t default_port = 80; + struct service_data *j; + apr_pool_t *t; + + ap_assert(r); + + apr_pool_create(&t, r->pool); + + for (v = ap_conftree; v; v = v->next) { + const char *a = v->args; + + if (strcasecmp(v->directive, "ServerName") == 0) + default_host_name = ap_getword_conf(t, &a); + else if (strcasecmp(v->directive, "Port") == 0) + default_port = (uint16_t) atoi(ap_getword_conf(t, &a)); + else if (strcasecmp(v->directive, "services; + + if ((colon = strchr(v->args, ':'))) + port = (uint8_t) atoi(colon+1); + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "VHOST: %s ", v->directive); */ + + for (l = v->first_child; l; l = l->next) { + a = l->args; + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "VHOST_INTERNAL %s | %s | %s | %s", l->directive, l->args, vname, vtypes); */ + + if (strcasecmp(l->directive, "ServerName") == 0) + host_name = ap_getword_conf(t, &a); + else if (strcasecmp(l->directive, "Port") == 0) + port = (uint16_t) atoi(ap_getword_conf(t, &a)); + else if (strcasecmp(l->directive, "DNSSDServiceName") == 0) + vname = ap_getword_conf(t, &a); + else if (strcasecmp(l->directive, "DNSSDServiceTypes") == 0) + vtypes = a; + else if (strcasecmp(l->directive, "args); + + if (*path != 0 && (path[(i = strlen(path) - 1)] == '>')) + path[i] = 0; + + for (s = l->first_child; s; s = s->next) { + a = s->args; + + if (strcasecmp(s->directive, "DNSSDServiceName") == 0) + sname = ap_getword_conf(t, &a); + else if (strcasecmp(s->directive, "DNSSDServiceTypes") == 0) + stypes = a; + } + + if (sname) + add_service(r, NULL, 0, path, sname, stypes, 0); + } + } + + /* Fill in missing data in based services */ + for (j = r->services; j && j != marker; j = j->next) { + j->port = port; + j->host_name = apr_pstrdup(r->pool, host_name); + } + + if (r->global_config_data->vhost || vname || vtypes) + add_service(r, host_name, port, NULL, vname ? vname : host_name, vtypes, 0); + } + } + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "ping"); */ + + if (r->global_config_data->user_dir) { + struct passwd *pw; + apr_pool_t *p_loop; + + apr_pool_create(&p_loop, t); + + while ((pw = getpwent())) { + apr_finfo_t finfo; + char *path; + const char *u; + + apr_pool_clear(p_loop); + + if (pw->pw_uid <= 500) + continue; + + if (*pw->pw_dir == 0 || strcmp(pw->pw_dir, "/") == 0) + continue; + + path = apr_pstrcat(p_loop, pw->pw_dir, "/public_html", NULL); + + if (apr_stat(&finfo, path, APR_FINFO_TYPE, p_loop) != APR_SUCCESS) + continue; + + if (finfo.filetype != APR_DIR) + continue; + + if (access(path, X_OK) != 0) + continue; + + if (pw->pw_gecos && *pw->pw_gecos) { + char *comma; + u = apr_pstrdup(p_loop, pw->pw_gecos); + if ((comma = strchr(u, ','))) + *comma = 0; + } else + u = pw->pw_name; + + add_service(r, NULL, 0, apr_pstrcat(p_loop, "/~", pw->pw_name), apr_pstrcat(p_loop, u, " on ", NULL), NULL, 1); + } + + endpwent(); + + apr_pool_destroy(p_loop); + } + + /* Fill in missing data in all services */ + for (j = r->services; j; j = j->next) { + if (!j->port) + j->port = default_port; + + if (!j->host_name) + j->host_name = apr_pstrdup(r->pool, default_host_name); + + if (!j->name) + j->name = apr_pstrdup(r->pool, j->host_name); + } + + apr_pool_destroy(t); +} + +static void create_service(struct service_data *j); + +static void service_callback(AVAHI_GCC_UNUSED AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + struct service_data *j = userdata; + + switch (state) { + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + case AVAHI_ENTRY_GROUP_ESTABLISHED: + break; + + case AVAHI_ENTRY_GROUP_COLLISION: { + + char *n; + ap_assert(j->chosen_name); + + n = avahi_alternative_service_name(j->chosen_name); + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, j->runtime->main_server, "Name collision on '%s', changing to '%s'", j->chosen_name, n); + + apr_pool_clear(j->pool); + j->chosen_name = apr_pstrdup(j->pool, n); + + create_service(j); + + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE: + ap_log_error(APLOG_MARK, APLOG_ERR, 0, j->runtime->main_server, "Failed to register service: %s", avahi_strerror(avahi_client_errno(j->runtime->client))); + break; + } +} + +static void create_service(struct service_data *j) { + apr_pool_t *t; + const char *n; + char *p; + struct runtime_data *r = j->runtime; + + if (!j->group) + if (!(j->group = avahi_entry_group_new(r->client, service_callback, j))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(r->client))); + return; + } + + ap_assert(j->group); + ap_assert(avahi_entry_group_is_empty(j->group)); + + apr_pool_create(&t, r->pool); + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "Service <%s>, host <%s>, port <%u>, location <%s>", j->name, j->host_name, j->port, j->location); */ + + if (j->chosen_name) + n = j->chosen_name; + else if (!j->name) + n = avahi_client_get_host_name(r->client); + else if (j->append_host_name) + n = apr_pstrcat(t, j->name, avahi_client_get_host_name(r->client), NULL); + else + n = j->name; + + if (!j->pool) + apr_pool_create(&j->pool, r->pool); + + if (n != j->chosen_name) { + apr_pool_clear(j->pool); + j->chosen_name = apr_pstrdup(j->pool, n); + } + + p = j->location ? apr_pstrcat(t, "path=", j->location, NULL) : NULL; + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "%s, %s", p, n); */ + + if (apr_is_empty_array(j->types)) { + + if (avahi_entry_group_add_service( + j->group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + n, + j->port == 443 ? "_https._tcp" : "_http._tcp", + NULL, + j->host_name, + j->port, + p, + NULL) < 0) { + + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "avahi_entry_group_add_service(\"%s\") failed: %s\n", n, avahi_strerror(avahi_client_errno(r->client))); + } + } else { + + char **type; + + for (type = (char**) j->types->elts; *type; type++) { + + if (avahi_entry_group_add_service( + j->group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + 0, + n, + *type, + NULL, + j->host_name, + j->port, + p, + NULL) < 0) { + + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "avahi_entry_group_add_service(\"%s\") failed: %s\n", n, avahi_strerror(avahi_client_errno(r->client))); + } + + } + + } + + if (avahi_entry_group_is_empty(j->group)) { + avahi_entry_group_free(j->group); + j->group = NULL; + } else + avahi_entry_group_commit(j->group); + + apr_pool_destroy(t); +} + +static void create_all_services(struct runtime_data *r) { + struct service_data *j; + ap_assert(r); + + for (j = r->services; j; j = j->next) + create_service(j); +} + +static void reset_services(struct runtime_data *r) { + struct service_data *j; + + ap_assert(r); + + for (j = r->services; j; j = j->next) { + if (j->group) + avahi_entry_group_reset(j->group); + + if (j->pool) + apr_pool_clear(j->pool); + + j->chosen_name = NULL; + } +} + +static void free_services(struct runtime_data *r) { + struct service_data *j; + + ap_assert(r); + + for (j = r->services; j; j = j->next) { + + if (j->group) { + avahi_entry_group_free(j->group); + j->group = NULL; + } + + if (j->pool) + apr_pool_clear(j->pool); + + j->chosen_name = NULL; + } +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { + struct runtime_data *r = userdata; + + ap_assert(r); + + r->client = c; + +/* ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->main_server, "client_callback(%u)", state); */ + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + create_all_services(r); + break; + + case AVAHI_CLIENT_S_COLLISION: + reset_services(r); + break; + + case AVAHI_CLIENT_FAILURE: + + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + int error; + + free_services(r); + avahi_client_free(r->client); + + if ((r->client = avahi_client_new(avahi_simple_poll_get(r->simple_poll), AVAHI_CLIENT_NO_FAIL, client_callback, r, &error))) + break; + + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "avahi_client_new() failed: %s", avahi_strerror(error)); + } else + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->main_server, "Client failure: %s", avahi_strerror(avahi_client_errno(c))); + + avahi_simple_poll_quit(r->simple_poll); + + break; + + case AVAHI_CLIENT_CONNECTING: + case AVAHI_CLIENT_S_REGISTERING: + break; + } + +} + +static void sigterm(AVAHI_GCC_UNUSED int s) { + const char c = 'x'; + write(sigterm_pipe_fds[1], &c, sizeof(c)); +} + +static void watch_callback(AvahiWatch *w, int fd, AvahiWatchEvent event, void *userdata) { + char c; + ssize_t l; + struct runtime_data *r = userdata; + + ap_assert(w); + ap_assert(fd == sigterm_pipe_fds[0]); + ap_assert(event == AVAHI_WATCH_IN); + ap_assert(r); + + l = read(fd, &c, sizeof(c)); + ap_assert(l == sizeof(c)); + + avahi_simple_poll_quit(r->simple_poll); +} + +static void child_process(apr_pool_t *p, server_rec *server, struct global_config_data *d) { + struct runtime_data r; + int error; + const AvahiPoll *api; + AvahiWatch *w; + + ap_assert(d); + + unixd_setup_child(); + + if (pipe(sigterm_pipe_fds) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "pipe() failed: %s", strerror(errno)); + goto quit; + } + + set_nonblock(sigterm_pipe_fds[0]); + set_nonblock(sigterm_pipe_fds[1]); + + apr_signal(SIGTERM, sigterm); + + r.main_server = server; + r.global_config_data = d; + r.client = NULL; + r.simple_poll = NULL; + r.services = NULL; + apr_pool_create(&r.pool, p); + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "Child process startup pid=%lu", (unsigned long) getpid()); */ + + assemble_services(&r); + + if (!r.services) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r.main_server, __FILE__": No services found to register"); + goto quit; + } + + if (!(r.simple_poll = avahi_simple_poll_new())) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "avahi_simple_poll_new() failed: %s", strerror(errno)); + goto quit; + } + + api = avahi_simple_poll_get(r.simple_poll); + w = api->watch_new(api, sigterm_pipe_fds[0], AVAHI_WATCH_IN, watch_callback, &r); + ap_assert(w); + + if (!(r.client = avahi_client_new(avahi_simple_poll_get(r.simple_poll), AVAHI_CLIENT_NO_FAIL, client_callback, &r, &error))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "avahi_client_new() failed: %s", avahi_strerror(error)); + goto quit; + } + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "Child process running"); */ + + avahi_simple_poll_loop(r.simple_poll); + +quit: + + if (r.client) + avahi_client_free(r.client); + + if (r.simple_poll) + avahi_simple_poll_free(r.simple_poll); + + if (r.pool) + apr_pool_destroy(r.pool); + + if (sigterm_pipe_fds[0] >= 0) + close(sigterm_pipe_fds[0]); + + if (sigterm_pipe_fds[1] >= 0) + close(sigterm_pipe_fds[1]); + + sigterm_pipe_fds[0] = sigterm_pipe_fds[1] = -1; + +/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, r.main_server, "Child process ending"); */ +} + +static int start_child_process(apr_pool_t *p, server_rec *server, struct global_config_data *d) { + apr_proc_t* proc; + apr_status_t status; + +/* ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server, "Spawning child pid=%lu", (unsigned long) getpid()); */ + + proc = apr_palloc(p, sizeof(apr_proc_t)); + ap_assert(proc); + + switch (status = apr_proc_fork(proc, p)) { + + case APR_INCHILD: + child_process(p, server, d); + exit(1); + /* never reached */ + break; + + case APR_INPARENT: + apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT); +/* ap_log_error(APLOG_MARK, APLOG_NOTICE, status, server, "Child process %lu", (unsigned long) proc->pid); */ + + break; + + default: + ap_log_error(APLOG_MARK, APLOG_ERR, status, server, "apr_proc_fork() failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + return OK; +} + +static int post_config( + apr_pool_t *pconf, + AVAHI_GCC_UNUSED apr_pool_t *plog, + AVAHI_GCC_UNUSED apr_pool_t *ptemp, + server_rec *s) { + + void *flag; + struct global_config_data *d = GET_CONFIG_DATA(s); + + /* All post_config hooks are called twice, we're only interested in the second call. */ + + apr_pool_userdata_get(&flag, MOD_DNSSD_USERDATA_KEY, s->process->pool); + if (!flag) { + apr_pool_userdata_set((void*) 1, MOD_DNSSD_USERDATA_KEY, apr_pool_cleanup_null, s->process->pool); + return OK; + } + + if (d->enabled) + return start_child_process(pconf, s, d); + + return OK; +} + +static void register_hooks(AVAHI_GCC_UNUSED apr_pool_t *p){ + ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_LAST); +} + +static void *create_server_config(apr_pool_t *p, AVAHI_GCC_UNUSED server_rec *s) { + struct global_config_data *d; + + d = apr_palloc(p, sizeof(struct global_config_data)); + ap_assert(d); + + d->enabled = 0; + d->user_dir = 1; + d->vhost = 1; + + return d; +} + +static const char *cmd_dnssd_enable( + cmd_parms *cmd, + AVAHI_GCC_UNUSED void *mconfig, + int enable) { + + struct global_config_data *d = GET_CONFIG_DATA(cmd->server); + const char *err; + + if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) + return err; + + d->enabled = enable; + return NULL; +} + +static const char *cmd_dnssd_enable_user_dir( + cmd_parms *cmd, + AVAHI_GCC_UNUSED void *mconfig, + int enable) { + + struct global_config_data *d = GET_CONFIG_DATA(cmd->server); + const char *err; + + if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) + return err; + + d->user_dir = enable; + return NULL; +} + +static const char *cmd_dnssd_enable_vhost( + cmd_parms *cmd, + AVAHI_GCC_UNUSED void *mconfig, + int enable) { + + struct global_config_data *d = GET_CONFIG_DATA(cmd->server); + const char *err; + + if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) + return err; + + d->vhost = enable; + return NULL; +} + +static const char *cmd_dnssd_service_name( + cmd_parms *cmd, + AVAHI_GCC_UNUSED void *mconfig, + const char *value) { + + const char *err; + + if ((err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|NOT_IN_LIMIT))) + return err; + + if (!avahi_is_valid_service_name(value)) + return "Invalid service name"; + + return NULL; +} + +static const char *cmd_dnssd_service_type( + cmd_parms *cmd, + AVAHI_GCC_UNUSED void *mconfig, + const char *value) { + + const char *err; + + if ((err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|NOT_IN_LIMIT))) + return err; + + if (!avahi_is_valid_service_type_strict(value)) + return "Invalid service type"; + + return NULL; +} + +static const command_rec commands[] = { + + AP_INIT_FLAG( + "DNSSDEnable", + cmd_dnssd_enable, + NULL, + RSRC_CONF, + "Enable/disable DNS-SD registration entirely (default: no)"), + + AP_INIT_FLAG( + "DNSSDAutoRegisterUserDir", + cmd_dnssd_enable_user_dir, + NULL, + RSRC_CONF, + "Enable/disable DNS-SD registration of ~/public_html (default: yes)"), + + AP_INIT_FLAG( + "DNSSDAutoRegisterVHosts", + cmd_dnssd_enable_vhost, + NULL, + RSRC_CONF, + "Enable/disable DNS-SD registration of all virtual hosts (default: yes)"), + + AP_INIT_TAKE1( + "DNSSDServiceName", + cmd_dnssd_service_name, + NULL, + OR_OPTIONS, + "Set the DNS-SD service name"), + + AP_INIT_ITERATE( + "DNSSDServiceTypes", + cmd_dnssd_service_type, + NULL, + OR_OPTIONS, + "Set on or more DNS-SD service types"), + + { NULL } +}; + +module AP_MODULE_DECLARE_DATA dnssd_module = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + create_server_config, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + commands, /* table of config file commands */ + register_hooks /* register hooks */ +}; + -- cgit