#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
#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 */
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];
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();
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;
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
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);
#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"
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)
};
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)
};
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) \
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
};
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 = {
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
};
}
/* </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. */
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;
}
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;
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;
#include <stdlib.h>
#include <unistd.h>
+#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#include <sys/wait.h>
/* 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++;
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);
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)
{
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))
#include <unistd.h>
#include <signal.h>
-#include <pwd.h>
-#include <grp.h>
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,