Mercurial > dovecot > core-2.2
changeset 9226:0ca7ed260f1e HEAD
master: Added back some startup checks/fixes.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 05 May 2009 14:55:05 -0400 |
parents | b510683ab83c |
children | f155917f1615 |
files | src/master/common.h src/master/main.c src/master/master-settings.c src/master/master-settings.h src/master/service-process.c src/master/service.c |
diffstat | 6 files changed, 342 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- a/src/master/common.h Tue May 05 14:54:28 2009 -0400 +++ b/src/master/common.h Tue May 05 14:55:05 2009 -0400 @@ -5,12 +5,19 @@ #include "master-interface.h" #include "master-settings.h" +#define AUTH_SUCCESS_PATH PKG_STATEDIR"/auth-success" + extern struct master_service *master_service; extern uid_t master_uid; extern gid_t master_gid; +extern bool auth_success_written; extern bool core_dumps_disabled; extern int null_fd; void process_exec(const char *cmd, const char *extra_args[]) ATTR_NORETURN; +int get_uidgid(const char *user, uid_t *uid_r, gid_t *gid_r, + const char **error_r); +int get_gid(const char *group, gid_t *gid_r, const char **error_r); + #endif
--- a/src/master/main.c Tue May 05 14:54:28 2009 -0400 +++ b/src/master/main.c Tue May 05 14:55:05 2009 -0400 @@ -3,6 +3,7 @@ #include "common.h" #include "lib-signals.h" #include "fd-close-on-exec.h" +#include "array.h" #include "write-full.h" #include "env-util.h" #include "hostpid.h" @@ -20,17 +21,22 @@ #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> +#include <pwd.h> +#include <grp.h> +#define FATAL_FILENAME "master-fatal.lastlog" #define MASTER_PID_FILE_NAME "master.pid" struct master_service *master_service; uid_t master_uid; gid_t master_gid; +bool auth_success_written; bool core_dumps_disabled; int null_fd; static char *pidfile_path; static struct service_list *services; +static fatal_failure_callback_t *orig_fatal_callback; static const char *child_process_env[3]; /* @UNSAFE */ @@ -66,6 +72,140 @@ i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", executable); } +int get_uidgid(const char *user, uid_t *uid_r, gid_t *gid_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; + *gid_r = pw->pw_gid; + return 0; +} + +int get_gid(const char *group, gid_t *gid_r, const char **error_r) +{ + struct group *gr; + + if (*group == '\0') { + *gid_r = (gid_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 void ATTR_NORETURN ATTR_FORMAT(3, 0) +master_fatal_callback(enum log_type type, int status, + const char *format, va_list args) +{ + const char *path, *str; + va_list args2; + int fd; + + /* if we already forked a child process, this isn't fatal for the + main process and there's no need to write the fatal file. */ + if (getpid() == strtol(my_pid, NULL, 10)) { + /* write the error message to a file (we're chdired to + base dir) */ + path = t_strconcat(FATAL_FILENAME, NULL); + fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd != -1) { + VA_COPY(args2, args); + str = t_strdup_vprintf(format, args2); + write_full(fd, str, strlen(str)); + (void)close(fd); + } + } + + orig_fatal_callback(type, status, format, args); + abort(); /* just to silence the noreturn attribute warnings */ +} + +static void fatal_log_check(const struct master_settings *set) +{ + const char *path; + char buf[1024]; + ssize_t ret; + int fd; + + path = t_strconcat(set->base_dir, "/"FATAL_FILENAME, NULL); + fd = open(path, O_RDONLY); + if (fd == -1) + return; + + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) + i_error("read(%s) failed: %m", path); + else { + buf[ret] = '\0'; + i_warning("Last died with error (see error log for more " + "information): %s", buf); + } + + close(fd); + if (unlink(path) < 0) + i_error("unlink(%s) failed: %m", path); +} + +static bool services_have_auth_destinations(const struct master_settings *set) +{ + struct service_settings *const *services; + unsigned int i, count; + + services = array_get(&set->services, &count); + for (i = 0; i < count; i++) { + if (strcmp(services[i]->type, "auth-destination") == 0) + return TRUE; + } + return FALSE; +} + +static bool auths_have_debug(const struct master_settings *set) +{ + struct master_auth_settings *const *auths; + unsigned int i, count; + + if (!array_is_created(&set->auths)) + return FALSE; + + auths = array_get(&set->auths, &count); + for (i = 0; i < count; i++) { + if (auths[i]->debug) + return TRUE; + } + return FALSE; +} + +static void auth_warning_print(const struct master_settings *set) +{ + struct stat st; + + auth_success_written = stat(AUTH_SUCCESS_PATH, &st) == 0; + if (!auth_success_written && !auths_have_debug(set) && + services_have_auth_destinations(set)) { + i_info("If you have trouble with authentication failures,\n" + "enable auth_debug setting. " + "See http://wiki.dovecot.org/WhyDoesItNotWork"); + + } +} + static void pid_file_check_running(const char *path) { char buf[32]; @@ -184,7 +324,7 @@ i_info(STARTUP_STRING); } -static void main_init(const struct master_settings *set, bool log_error) +static void main_init(bool log_error) { drop_capabilities(); @@ -370,6 +510,7 @@ struct master_settings *set; unsigned int child_process_env_idx = 0; const char *getopt_str, *error, *env_tz; + failure_callback_t *error_callback; void **sets; bool foreground = FALSE, ask_key_pass = FALSE, log_error = FALSE; int c; @@ -443,6 +584,8 @@ if (!log_error) { pid_file_check_running(pidfile_path); master_settings_do_fixes(set); + fatal_log_check(set); + auth_warning_print(set); } #if 0 // FIXME @@ -487,12 +630,16 @@ return FATAL_DEFAULT; master_service_init_log(master_service, "dovecot: ", 0); + i_get_failure_handlers(&orig_fatal_callback, &error_callback, + &error_callback); + i_set_fatal_handler(master_fatal_callback); + if (!foreground) daemonize(); if (chdir(set->base_dir) < 0) i_fatal("chdir(%s) failed: %m", set->base_dir); - main_init(set, log_error); + main_init(log_error); master_service_run(master_service, NULL); main_deinit(); master_service_deinit(&master_service);
--- a/src/master/master-settings.c Tue May 05 14:54:28 2009 -0400 +++ b/src/master/master-settings.c Tue May 05 14:55:05 2009 -0400 @@ -4,7 +4,9 @@ #include "array.h" #include "env-util.h" #include "istream.h" +#include "str.h" #include "mkdir-parents.h" +#include "safe-mkdir.h" #include "settings-parser.h" #include "master-settings.h" @@ -45,6 +47,7 @@ MEMBER(parent) &service_setting_parser_info, MEMBER(parent_offset) (size_t)-1, + MEMBER(type_offset) (size_t)-1, MEMBER(struct_size) sizeof(struct file_listener_settings) }; @@ -72,6 +75,7 @@ MEMBER(parent) &service_setting_parser_info, MEMBER(parent_offset) (size_t)-1, + MEMBER(type_offset) (size_t)-1, MEMBER(struct_size) sizeof(struct inet_listener_settings) }; @@ -147,6 +151,32 @@ }; #undef DEF +#define DEF(type, name) \ + { type, #name, offsetof(struct master_auth_settings, name), NULL } + +static struct setting_define master_auth_setting_defines[] = { + DEF(SET_BOOL, debug), + + SETTING_DEFINE_LIST_END +}; + +static struct master_auth_settings master_auth_default_settings = { + MEMBER(debug) FALSE +}; + +struct setting_parser_info master_auth_setting_parser_info = { + MEMBER(defines) master_auth_setting_defines, + MEMBER(defaults) &master_auth_default_settings, + + MEMBER(parent) &master_setting_parser_info, + MEMBER(dynamic_parsers) NULL, + + MEMBER(parent_offset) (size_t)-1, + MEMBER(type_offset) (size_t)-1, + MEMBER(struct_size) sizeof(struct master_auth_settings) +}; + +#undef DEF #undef DEFLIST #define DEF(type, name) \ { type, #name, offsetof(struct master_settings, name), NULL } @@ -167,6 +197,7 @@ DEF(SET_UINT, last_valid_gid), DEFLIST(services, "service", &service_setting_parser_info), + DEFLIST(auths, "auth", &master_auth_setting_parser_info), SETTING_DEFINE_LIST_END }; @@ -184,7 +215,8 @@ MEMBER(first_valid_gid) 1, MEMBER(last_valid_gid) 0, - MEMBER(services) ARRAY_INIT + MEMBER(services) ARRAY_INIT, + MEMBER(auths) ARRAY_INIT }; struct setting_parser_info master_setting_parser_info = { @@ -193,6 +225,7 @@ MEMBER(parent) NULL, MEMBER(parent_offset) (size_t)-1, + MEMBER(type_offset) (size_t)-1, MEMBER(struct_size) sizeof(struct master_settings), MEMBER(check_func) master_settings_verify }; @@ -284,10 +317,111 @@ } /* </settings checks> */ +static bool +login_want_core_dumps(const struct master_settings *set, gid_t *gid_r) +{ + struct service_settings *const *services; + unsigned int i, count; + const char *error; + bool cores = FALSE; + uid_t uid; + + *gid_r = (gid_t)-1; + + services = array_get(&set->services, &count); + for (i = 0; i < count; i++) { + if (strcmp(services[i]->type, "auth-source") == 0 && + strstr(services[i]->name, "-login") != NULL) { + if (strstr(services[i]->executable, " -D") != NULL) + cores = TRUE; + (void)get_uidgid(services[i]->user, &uid, gid_r, &error); + if (*services[i]->group != '\0') + (void)get_gid(services[i]->group, gid_r, &error); + } + } + return cores; +} + +static bool +settings_have_auth_unix_listeners_in(const struct master_settings *set, + const char *dir) +{ + struct service_settings *const *services; + struct file_listener_settings *const *u; + unsigned int i, j, count, count2; + + services = array_get(&set->services, &count); + for (i = 0; i < count; i++) { + if (strcmp(services[i]->type, "auth") == 0 && + array_is_created(&services[i]->unix_listeners)) { + u = array_get(&services[i]->unix_listeners, &count2); + for (j = 0; j < count2; j++) { + if (strncmp(u[j]->path, dir, strlen(dir)) == 0) + return TRUE; + } + } + } + return FALSE; +} + +static void unlink_sockets(const char *path, const char *prefix) +{ + DIR *dirp; + struct dirent *dp; + struct stat st; + string_t *str; + unsigned int prefix_len; + + dirp = opendir(path); + if (dirp == NULL) { + i_error("opendir(%s) failed: %m", path); + return; + } + + prefix_len = strlen(prefix); + str = t_str_new(256); + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.') + continue; + + if (strncmp(dp->d_name, prefix, prefix_len) != 0) + continue; + + str_truncate(str, 0); + str_printfa(str, "%s/%s", path, dp->d_name); + if (lstat(str_c(str), &st) < 0) { + if (errno != ENOENT) + i_error("lstat(%s) failed: %m", str_c(str)); + continue; + } + if (!S_ISSOCK(st.st_mode)) + continue; + + /* try to avoid unlinking sockets if someone's already + listening in them. do this only at startup, because + when SIGHUPing a child process might catch the new + connection before it notices that it's supposed + to die. null_fd == -1 check is a bit kludgy, but works.. */ + if (null_fd == -1) { + int fd = net_connect_unix(str_c(str)); + if (fd != -1 || errno != ECONNREFUSED) { + i_fatal("Dovecot is already running? " + "Socket already exists: %s", + str_c(str)); + } + } + + if (unlink(str_c(str)) < 0 && errno != ENOENT) + i_error("unlink(%s) failed: %m", str_c(str)); + } + (void)closedir(dirp); +} + bool master_settings_do_fixes(const struct master_settings *set) { const char *login_dir; struct stat st; + gid_t gid; /* since base dir is under /var/run by default, it may have been deleted. */ @@ -305,17 +439,36 @@ return FALSE; } - /* create login directory under base dir */ - login_dir = t_strconcat(set->base_dir, "/login", NULL); - if (mkdir(login_dir, 0755) < 0 && errno != EEXIST) { - i_error("mkdir(%s) failed: %m", login_dir); - return FALSE; - } - /* Make sure our permanent state directory exists */ if (mkdir_parents(PKG_STATEDIR, 0750) < 0 && errno != EEXIST) { i_error("mkdir(%s) failed: %m", PKG_STATEDIR); return FALSE; } + + /* remove auth worker sockets left by unclean exits */ + unlink_sockets(set->base_dir, "auth-worker."); + + login_dir = t_strconcat(set->base_dir, "/login", NULL); + if (settings_have_auth_unix_listeners_in(set, login_dir)) { + /* we are not using external authentication, so make sure the + login directory exists with correct permissions and it's + empty. with external auth we wouldn't want to delete + existing sockets or break the permissions required by the + auth server. */ + mode_t mode = login_want_core_dumps(set, &gid) ? 0770 : 0750; + if (gid != (gid_t)-1 && + safe_mkdir(login_dir, mode, master_uid, gid) == 0) { + i_warning("Corrected permissions for login directory " + "%s", login_dir); + } + + unlink_sockets(login_dir, ""); + } else { + /* still make sure that login directory exists */ + if (mkdir(login_dir, 0755) < 0 && errno != EEXIST) { + i_error("mkdir(%s) failed: %m", login_dir); + return FALSE; + } + } return TRUE; }
--- a/src/master/master-settings.h Tue May 05 14:54:28 2009 -0400 +++ b/src/master/master-settings.h Tue May 05 14:55:05 2009 -0400 @@ -39,6 +39,10 @@ ARRAY_DEFINE(inet_listeners, struct inet_listener_settings *); }; +struct master_auth_settings { + bool debug; +}; + struct master_settings { const char *base_dir; const char *libexec_dir; @@ -51,6 +55,7 @@ unsigned int first_valid_gid, last_valid_gid; ARRAY_DEFINE(services, struct service_settings *); + ARRAY_DEFINE(auths, struct master_auth_settings *); }; extern struct setting_parser_info master_setting_parser_info;
--- a/src/master/service-process.c Tue May 05 14:54:28 2009 -0400 +++ b/src/master/service-process.c Tue May 05 14:55:05 2009 -0400 @@ -24,6 +24,7 @@ #include <stdlib.h> #include <unistd.h> +#include <fcntl.h> #include <syslog.h> #include <signal.h> #include <sys/wait.h> @@ -56,7 +57,8 @@ /* first add non-ssl listeners */ for (i = 0; i < count; i++) { if (listeners[i]->fd != -1 && - !listeners[i]->set.inetset.set->ssl) { + (listeners[i]->type != SERVICE_LISTENER_INET || + !listeners[i]->set.inetset.set->ssl)) { dup2_append(&dups, listeners[i]->fd, MASTER_LISTEN_FD_FIRST + n); n++; socket_listener_count++; @@ -66,6 +68,7 @@ ssl_socket_count = 0; for (i = 0; i < count; i++) { if (listeners[i]->fd != -1 && + listeners[i]->type == SERVICE_LISTENER_INET && listeners[i]->set.inetset.set->ssl) { dup2_append(&dups, listeners[i]->fd, MASTER_LISTEN_FD_FIRST + n); @@ -197,6 +200,21 @@ env_put(str_c(expanded_vars)); } +static void auth_success_write(void) +{ + int fd; + + if (auth_success_written) + return; + + fd = creat(AUTH_SUCCESS_PATH, 0666); + if (fd == -1) + i_error("creat(%s) failed: %m", AUTH_SUCCESS_PATH); + else + (void)close(fd); + auth_success_written = TRUE; +} + static void drop_privileges(struct service *service, const char *const *auth_args) { @@ -224,6 +242,7 @@ user = auth_args[0]; env_put(t_strconcat("USER=", user, NULL)); + auth_success_write(); auth_args_apply(auth_args + 1, &rset, &home); if (!validate_uid_gid(master_set, rset.uid, rset.gid, user))
--- a/src/master/service.c Tue May 05 14:54:28 2009 -0400 +++ b/src/master/service.c Tue May 05 14:55:05 2009 -0400 @@ -11,8 +11,6 @@ #include <unistd.h> #include <signal.h> -#include <pwd.h> -#include <grp.h> void service_error(struct service *service, const char *format, ...) { @@ -24,44 +22,6 @@ va_end(args); } -static int get_uidgid(const char *user, uid_t *uid_r, gid_t *gid_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; - *gid_r = pw->pw_gid; - 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 = (gid_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,