]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
master: Added back some startup checks/fixes.
authorTimo Sirainen <tss@iki.fi>
Tue, 5 May 2009 18:55:05 +0000 (14:55 -0400)
committerTimo Sirainen <tss@iki.fi>
Tue, 5 May 2009 18:55:05 +0000 (14:55 -0400)
--HG--
branch : HEAD

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

index 50e39194804589c1881fff8738b38d311c07c683..0e605c491393c45438a2c9131865f1aee7d3055b 100644 (file)
@@ -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
index aa8bacba725c2a38a28e483e356b8090d1b0406a..e8b549f805bdfa77a3c4fb2ba30ccee452c2d835 100644 (file)
@@ -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"
 #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 @@ void process_exec(const char *cmd, const char *extra_args[])
        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 @@ static void main_log_startup(void)
                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 @@ int main(int argc, char *argv[])
        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 @@ int main(int argc, char *argv[])
        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 @@ int main(int argc, char *argv[])
                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);
index ac28b57cab79982f7a7effe32169dc5950f5c6d5..2b0be8f69abaac84650b835ca451730ac0061ca5 100644 (file)
@@ -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 @@ static struct setting_parser_info file_listener_setting_parser_info = {
 
        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 @@ static struct setting_parser_info inet_listener_setting_parser_info = {
 
        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)
 };
 
@@ -146,6 +150,32 @@ struct setting_parser_info service_setting_parser_info = {
        MEMBER(struct_size) sizeof(struct service_settings)
 };
 
+#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) \
@@ -167,6 +197,7 @@ static struct setting_define master_setting_defines[] = {
        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 @@ static struct master_settings master_default_settings = {
        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 @@ struct setting_parser_info master_setting_parser_info = {
 
        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 @@ master_settings_verify(void *_set, pool_t pool, const char **error_r)
 }
 /* </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 @@ bool master_settings_do_fixes(const struct master_settings *set)
                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;
 }
index 7d3ce973362ce716770cb9e18938d05bb840f441..403807feba9549ae7dff46c64889d2a2c4bff8c2 100644 (file)
@@ -39,6 +39,10 @@ struct service_settings {
        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 @@ struct master_settings {
        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;
index c32985aed9c76e80758070367ab0d5c6f995158c..3759e71026b2751b62d76bd22363570e97dd29d0 100644 (file)
@@ -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 @@ service_dup_fds(struct service *service, int auth_fd, int std_fd)
        /* 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 @@ service_dup_fds(struct service *service, int auth_fd, int std_fd)
        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 @@ static void auth_args_apply(const char *const *args,
        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 @@ static void drop_privileges(struct service *service,
                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))
index 437d072f40e7f6a56399a1b0ec2b124e8175ca3a..f0f48b7b4be6cc1160b3343e431242fde2bc2905 100644 (file)
@@ -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 @@ void service_error(struct service *service, const char *format, ...)
        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,