Mercurial > dovecot > core-2.2
view src/master/service.c @ 9159:6324a79d3ee1 HEAD
Initial commit for v2.0 master rewrite. Several features are still missing.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 23 Apr 2009 19:53:44 -0400 |
parents | |
children | 7b3accdf44f8 |
line wrap: on
line source
/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ #include "common.h" #include "array.h" #include "hash.h" #include "str.h" #include "service.h" #include "service-process.h" #include "service-monitor.h" #include <unistd.h> #include <signal.h> #include <pwd.h> #include <grp.h> static int get_uid(const char *user, uid_t *uid_r, const char **error_r) { struct passwd *pw; if (*user == '\0') { *uid_r = (uid_t)-1; return 0; } if ((pw = getpwnam(user)) == NULL) { *error_r = t_strdup_printf("User doesn't exist: %s", user); return -1; } *uid_r = pw->pw_uid; return 0; } static int get_gid(const char *group, gid_t *gid_r, const char **error_r) { struct group *gr; if (*group == '\0') { *gid_r = (uid_t)-1; return 0; } if ((gr = getgrnam(group)) == NULL) { *error_r = t_strdup_printf("Group doesn't exist: %s", group); return -1; } *gid_r = gr->gr_gid; return 0; } static struct service_listener * service_create_file_listener(struct service *service, enum service_listener_type type, const struct file_listener_settings *set, const char **error_r) { struct service_listener *l; l = p_new(service->list->pool, struct service_listener, 1); l->service = service; l->type = type; l->fd = -1; l->set.fileset.set = set; if (get_uid(set->user, &l->set.fileset.uid, error_r) < 0) return NULL; if (get_gid(set->group, &l->set.fileset.gid, error_r) < 0) return NULL; return l; } static int resolve_ip(const char *address, struct ip_addr *ip_r, const char **error_r) { struct ip_addr *ip_list; unsigned int ips_count; int ret; if (address == NULL || strcmp(address, "*") == 0) { /* IPv4 any */ net_get_ip_any4(ip_r); return 0; } if (strcmp(address, "::") == 0) { /* IPv6 any */ net_get_ip_any6(ip_r); return 0; } /* Return the first IP if there happens to be multiple. */ ret = net_gethostbyname(address, &ip_list, &ips_count); if (ret != 0) { *error_r = t_strdup_printf("Can't resolve address %s: %s", address, net_gethosterror(ret)); return -1; } if (ips_count < 1) { *error_r = t_strdup_printf("No IPs for address: %s", address); return -1; } if (ips_count > 1) { *error_r = t_strdup_printf("Multiple IPs for address: %s", address); return -1; } *ip_r = ip_list[0]; return 0; } static struct service_listener * service_create_inet_listener(struct service *service, const struct inet_listener_settings *set, const char **error_r) { struct service_listener *l; l = p_new(service->list->pool, struct service_listener, 1); l->service = service; l->type = SERVICE_LISTENER_INET; l->fd = -1; l->set.inetset.set = set; if (resolve_ip(set->address, &l->set.inetset.ip, error_r) < 0) return NULL; if (set->port == 0) { *error_r = "Port not given"; return NULL; } if (set->port > 65535) { *error_r = t_strdup_printf("Invalid port: %u", set->port); return NULL; } return l; } static struct service * service_create(pool_t pool, const struct service_settings *set, struct service_list *service_list, const char **error_r) { struct file_listener_settings *const *unix_listeners; struct file_listener_settings *const *fifo_listeners; struct inet_listener_settings *const *inet_listeners; struct service *service; struct service_listener *l; const char *p, *const *tmp; string_t *str; unsigned int i, unix_count, fifo_count, inet_count; service = p_new(pool, struct service, 1); service->list = service_list; service->set = set; service->type = SERVICE_TYPE_UNKNOWN; if (*set->type != '\0') { service->name = set->type; if (strcmp(set->type, "log") == 0) service->type = SERVICE_TYPE_LOG; else if (strcmp(set->type, "config") == 0) service->type = SERVICE_TYPE_CONFIG; else if (strcmp(set->type, "auth") == 0) service->type = SERVICE_TYPE_AUTH_SERVER; else service->name = NULL; } if (*set->auth_dest_service != '\0') service->type = SERVICE_TYPE_AUTH_SOURCE; if (set->process_limit == 0) { /* unlimited */ service->process_limit = INT_MAX; } else if (set->process_limit == (unsigned int)-1) { /* use default */ service->process_limit = set->master_set->default_process_limit; } else { service->process_limit = set->process_limit; } if (set->executable == NULL) { *error_r = "executable not given"; return NULL; } /* get service name from some of the unique types, fallback to executable name without path and parameters */ if (service->name == NULL) { p = strrchr(set->executable, '/'); if (p == NULL) service->name = t_strcut(set->executable, ' '); else service->name = t_strcut(p + 1, ' '); service->name = p_strdup(pool, service->name); } if (get_uid(set->user, &service->uid, error_r) < 0) return NULL; if (get_gid(set->group, &service->gid, error_r) < 0) return NULL; if (get_gid(set->privileged_group, &service->privileged_gid, error_r) < 0) return NULL; if (*set->extra_groups != '\0') { str = t_str_new(64); tmp = t_strsplit(set->extra_groups, ","); for (; *tmp != NULL; tmp++) { gid_t gid; if (get_gid(*tmp, &gid, error_r) < 0) return NULL; if (str_len(str) > 0) str_append_c(str, ','); str_append(str, dec2str(gid)); } service->extra_gids = p_strdup(pool, str_c(str)); } if (*set->executable == '/') service->executable = set->executable; else { service->executable = p_strconcat(pool, set->master_set->libexec_dir, "/", set->executable, NULL); } if (access(t_strcut(service->executable, ' '), X_OK) < 0) { *error_r = t_strdup_printf("access(%s) failed: %m", t_strcut(service->executable, ' ')); return NULL; } /* set these later, so if something fails we don't have to worry about closing them */ service->log_fd[0] = -1; service->log_fd[1] = -1; service->status_fd[0] = -1; service->status_fd[1] = -1; if (array_is_created(&set->unix_listeners)) unix_listeners = array_get(&set->unix_listeners, &unix_count); else { unix_listeners = NULL; unix_count = 0; } if (array_is_created(&set->fifo_listeners)) fifo_listeners = array_get(&set->unix_listeners, &fifo_count); else { fifo_listeners = NULL; fifo_count = 0; } if (array_is_created(&set->inet_listeners)) inet_listeners = array_get(&set->inet_listeners, &inet_count); else { inet_listeners = NULL; inet_count = 0; } if (unix_count == 0 && service->type == SERVICE_TYPE_CONFIG) { *error_r = "Service must have unix listeners"; return NULL; } p_array_init(&service->listeners, pool, unix_count + fifo_count + inet_count); for (i = 0; i < unix_count; i++) { l = service_create_file_listener(service, SERVICE_LISTENER_UNIX, unix_listeners[i], error_r); if (l == NULL) return NULL; array_append(&service->listeners, &l, 1); } for (i = 0; i < fifo_count; i++) { l = service_create_file_listener(service, SERVICE_LISTENER_UNIX, fifo_listeners[i], error_r); if (l == NULL) return NULL; array_append(&service->listeners, &l, 1); } for (i = 0; i < inet_count; i++) { l = service_create_inet_listener(service, inet_listeners[i], error_r); if (l == NULL) return NULL; array_append(&service->listeners, &l, 1); } return service; } static unsigned int pid_hash(const void *p) { const pid_t *pid = p; return (unsigned int)*pid; } static int pid_hash_cmp(const void *p1, const void *p2) { const pid_t *pid1 = p1, *pid2 = p2; return *pid1 < *pid2 ? -1 : *pid1 > *pid2 ? 1 : 0; } static struct service * service_lookup(struct service_list *service_list, const char *name) { struct service *const *services; unsigned int i, count; services = array_get(&service_list->services, &count); for (i = 0; i < count; i++) { if (strcmp(services[i]->name, name) == 0) return services[i]; } return NULL; } struct service_list * services_create(const struct master_settings *set, const char *const *child_process_env, const char **error_r) { struct service_list *service_list; struct service *service, *const *services; struct service_settings *const *service_settings; pool_t pool; const char *error; unsigned int i, count; pool = pool_alloconly_create("services pool", 4096); service_list = p_new(pool, struct service_list, 1); service_list->pool = pool; service_list->child_process_env = child_process_env; service_settings = array_get(&set->services, &count); p_array_init(&service_list->services, pool, count); for (i = 0; i < count; i++) { service = service_create(pool, service_settings[i], service_list, &error); if (service == NULL) { *error_r = t_strdup_printf("service(%s) %s", service_settings[i]->type == NULL ? "unknown" : service_settings[i]->type, error); return NULL; } switch (service->type) { case SERVICE_TYPE_LOG: if (service_list->log != NULL) { *error_r = "Multiple log services specified"; return NULL; } service_list->log = service; break; case SERVICE_TYPE_CONFIG: if (service_list->config != NULL) { *error_r = "Multiple config services specified"; return NULL; } service_list->config = service; break; default: break; } array_append(&service_list->services, &service, 1); } /* resolve service dependencies */ services = array_get(&service_list->services, &count); for (i = 0; i < count; i++) { if (services[i]->type == SERVICE_TYPE_AUTH_SOURCE) { const char *dest_service = services[i]->set->auth_dest_service; services[i]->auth_dest_service = service_lookup(service_list, dest_service); if (services[i]->auth_dest_service == NULL) { *error_r = t_strdup_printf( "auth_dest_service doesn't exist: %s", dest_service); return NULL; } } } if (service_list->log == NULL) { *error_r = "log service not specified"; return NULL; } if (service_list->config == NULL) { *error_r = "config process not specified"; return NULL; } service_list->pids = hash_table_create(default_pool, pool, 0, pid_hash, pid_hash_cmp); return service_list; } void service_signal(struct service *service, int signo) { struct hash_iterate_context *iter; void *key, *value; iter = hash_table_iterate_init(service->list->pids); while (hash_table_iterate(iter, &key, &value)) { struct service_process *process = value; if (process->service != service) continue; if (kill(process->pid, signo) < 0 && errno != ESRCH) { i_error("service(%s): kill(%s, %d) failed: %m", service->name, dec2str(process->pid), signo); } } hash_table_iterate_deinit(&iter); } void services_destroy(struct service_list *service_list) { struct hash_iterate_context *iter; void *key, *value; /* make sure we log if child processes died unexpectedly */ services_monitor_reap_children(service_list); services_monitor_stop(service_list); /* free all child process information */ iter = hash_table_iterate_init(service_list->pids); while (hash_table_iterate(iter, &key, &value)) service_process_destroy(value); hash_table_iterate_deinit(&iter); hash_table_destroy(&service_list->pids); pool_unref(&service_list->pool); }