From 0b4e1043e596bfb36d999dacbf1d4d63ee96d75f Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Fri, 4 Jun 2010 18:05:28 +0100 Subject: [PATCH] When creating shared directories, preserve parent dir's permissions if it has setgid bit enabled. This works only if location path uses %variables. The directories up to last variable are created by preserving parent directory's modes, while the rest of the directories are created with 0700. For example with "/var/mail/%d/%2n/%n/Maildir", "/var/mail/domain/nn" preserves /var/mail's permissions, while the "username/Maildir" directories have 0700 mode. --HG-- branch : HEAD --- src/lib-storage/index/dbox-multi/mdbox-file.c | 12 +- .../index/dbox-multi/mdbox-map-private.h | 3 +- src/lib-storage/index/dbox-multi/mdbox-map.c | 14 +- src/lib-storage/index/index-storage.c | 19 +-- src/lib-storage/index/maildir/maildir-util.c | 29 ++-- src/lib-storage/mail-namespace.c | 17 +- src/lib-storage/mail-namespace.h | 2 +- src/lib-storage/mailbox-list-private.h | 4 + src/lib-storage/mailbox-list.c | 154 ++++++++++++++++++ 9 files changed, 205 insertions(+), 49 deletions(-) diff --git a/src/lib-storage/index/dbox-multi/mdbox-file.c b/src/lib-storage/index/dbox-multi/mdbox-file.c index 84bee99705..b96a15f21d 100644 --- a/src/lib-storage/index/dbox-multi/mdbox-file.c +++ b/src/lib-storage/index/dbox-multi/mdbox-file.c @@ -13,6 +13,7 @@ #include "fdatasync-path.h" #include "eacces-error.h" #include "str.h" +#include "mailbox-list-private.h" #include "mdbox-storage.h" #include "mdbox-map-private.h" #include "mdbox-file.h" @@ -275,11 +276,12 @@ int mdbox_file_create_fd(struct dbox_file *file, const char *path, bool parents) if (fd == -1 && errno == ENOENT && parents && (p = strrchr(path, '/')) != NULL) { dir = t_strdup_until(path, p); - if (mkdir_parents_chgrp(dir, map->create_dir_mode, - map->create_gid, - map->create_gid_origin) < 0) { - mail_storage_set_critical(&file->storage->storage, - "mkdir_parents(%s) failed: %m", dir); + if (mailbox_list_mkdir(map->root_list, dir, + path != file->alt_path ? + MAILBOX_LIST_PATH_TYPE_DIR : + MAILBOX_LIST_PATH_TYPE_ALT_DIR) < 0) { + mail_storage_copy_list_error(&file->storage->storage, + map->root_list); return -1; } /* try again */ diff --git a/src/lib-storage/index/dbox-multi/mdbox-map-private.h b/src/lib-storage/index/dbox-multi/mdbox-map-private.h index 8bbeb78305..59a6a8151d 100644 --- a/src/lib-storage/index/dbox-multi/mdbox-map-private.h +++ b/src/lib-storage/index/dbox-multi/mdbox-map-private.h @@ -20,7 +20,8 @@ struct mdbox_map { uint32_t map_ext_id, ref_ext_id; - mode_t create_mode, create_dir_mode; + struct mailbox_list *root_list; + mode_t create_mode; gid_t create_gid; const char *create_gid_origin; }; diff --git a/src/lib-storage/index/dbox-multi/mdbox-map.c b/src/lib-storage/index/dbox-multi/mdbox-map.c index 9811d5c5d2..696f0e9c8f 100644 --- a/src/lib-storage/index/dbox-multi/mdbox-map.c +++ b/src/lib-storage/index/dbox-multi/mdbox-map.c @@ -6,6 +6,7 @@ #include "ostream.h" #include "mkdir-parents.h" #include "unlink-old-files.h" +#include "mailbox-list-private.h" #include "mdbox-storage.h" #include "mdbox-file.h" #include "mdbox-map-private.h" @@ -48,14 +49,13 @@ mdbox_map_init(struct mdbox_storage *storage, struct mailbox_list *root_list, const char *path) { struct mdbox_map *map; - gid_t tmp_gid; - const char *tmp_origin; map = i_new(struct mdbox_map, 1); map->storage = storage; map->set = storage->set; map->path = i_strdup(path); map->index = mail_index_alloc(path, MDBOX_GLOBAL_INDEX_PREFIX); + map->root_list = root_list; map->map_ext_id = mail_index_ext_register(map->index, "map", sizeof(struct mdbox_map_mail_index_header), sizeof(struct mdbox_map_mail_index_record), @@ -66,8 +66,6 @@ mdbox_map_init(struct mdbox_storage *storage, struct mailbox_list *root_list, mailbox_list_get_permissions(root_list, NULL, &map->create_mode, &map->create_gid, &map->create_gid_origin); - mailbox_list_get_dir_permissions(root_list, NULL, &map->create_dir_mode, - &tmp_gid, &tmp_origin); mail_index_set_permissions(map->index, map->create_mode, map->create_gid, map->create_gid_origin); return map; @@ -90,11 +88,9 @@ void mdbox_map_deinit(struct mdbox_map **_map) static int mdbox_map_mkdir_storage(struct mdbox_map *map) { - if (mkdir_parents_chgrp(map->path, map->create_dir_mode, - map->create_gid, map->create_gid_origin) < 0 && - errno != EEXIST) { - mail_storage_set_critical(MAP_STORAGE(map), - "mkdir(%s) failed: %m", map->path); + if (mailbox_list_mkdir(map->root_list, map->path, + MAILBOX_LIST_PATH_TYPE_DIR) < 0) { + mail_storage_copy_list_error(MAP_STORAGE(map), map->root_list); return -1; } return 0; diff --git a/src/lib-storage/index/index-storage.c b/src/lib-storage/index/index-storage.c index 85a531c1ea..119dabb8fb 100644 --- a/src/lib-storage/index/index-storage.c +++ b/src/lib-storage/index/index-storage.c @@ -29,11 +29,10 @@ struct index_storage_module index_storage_module = int index_list_create_missing_index_dir(struct mailbox_list *list, const char *name) { - const char *root_dir, *index_dir, *p, *parent_dir; - const char *origin, *parent_origin; - mode_t mode, parent_mode; - gid_t gid, parent_gid; - int n = 0; + const char *root_dir, *index_dir, *parent_dir, *p, *origin; + mode_t mode; + gid_t gid; + unsigned int n = 0; root_dir = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); @@ -54,16 +53,10 @@ int index_list_create_missing_index_dir(struct mailbox_list *list, return -1; } /* create the parent directory first */ - mailbox_list_get_dir_permissions(list, NULL, &parent_mode, - &parent_gid, &parent_origin); parent_dir = t_strdup_until(index_dir, p); - if (mkdir_parents_chgrp(parent_dir, parent_mode, - parent_gid, parent_origin) < 0 && - errno != EEXIST) { - mailbox_list_set_critical(list, - "mkdir(%s) failed: %m", parent_dir); + if (mailbox_list_mkdir(list, parent_dir, + MAILBOX_LIST_PATH_TYPE_INDEX) < 0) return -1; - } } return 0; } diff --git a/src/lib-storage/index/maildir/maildir-util.c b/src/lib-storage/index/maildir/maildir-util.c index 7ec527ded1..d0ef09d4e2 100644 --- a/src/lib-storage/index/maildir/maildir-util.c +++ b/src/lib-storage/index/maildir/maildir-util.c @@ -5,6 +5,7 @@ #include "ioloop.h" #include "str.h" #include "mkdir-parents.h" +#include "mailbox-list-private.h" #include "maildir-storage.h" #include "maildir-uidlist.h" #include "maildir-keywords.h" @@ -131,11 +132,9 @@ int maildir_file_do(struct maildir_mailbox *mbox, uint32_t uid, } static int maildir_create_path(struct mailbox *box, const char *path, - bool is_mail_dir) + enum mailbox_list_path_type type, bool retry) { - const char *p, *parent, *origin; - mode_t parent_mode; - gid_t parent_gid; + const char *p, *parent; if (mkdir_chgrp(path, box->dir_create_mode, box->file_create_gid, box->file_create_gid_origin) == 0) @@ -146,20 +145,17 @@ static int maildir_create_path(struct mailbox *box, const char *path, return 0; case ENOENT: p = strrchr(path, '/'); - if (is_mail_dir || p == NULL) { + if (type == MAILBOX_LIST_PATH_TYPE_MAILBOX || + p == NULL || !retry) { /* mailbox was being deleted just now */ mailbox_set_deleted(box); return -1; } /* create index/control root directory */ parent = t_strdup_until(path, p); - mailbox_list_get_dir_permissions(box->list, NULL, &parent_mode, - &parent_gid, &origin); - if (mkdir_parents_chgrp(parent, parent_mode, parent_gid, - origin) == 0 || - errno == EEXIST) { + if (mailbox_list_mkdir(box->list, parent, type) == 0) { /* should work now, try again */ - return maildir_create_path(box, path, TRUE); + return maildir_create_path(box, path, type, FALSE); } /* fall through */ path = parent; @@ -174,16 +170,20 @@ static int maildir_create_subdirs(struct mailbox *box) { static const char *subdirs[] = { "cur", "new", "tmp" }; const char *dirs[N_ELEMENTS(subdirs) + 2]; + enum mailbox_list_path_type types[N_ELEMENTS(subdirs) + 2]; struct stat st; const char *path; unsigned int i; - bool is_mail_dir; /* @UNSAFE: get a list of directories we want to create */ - for (i = 0; i < N_ELEMENTS(subdirs); i++) + for (i = 0; i < N_ELEMENTS(subdirs); i++) { + types[i] = MAILBOX_LIST_PATH_TYPE_MAILBOX; dirs[i] = t_strconcat(box->path, "/", subdirs[i], NULL); + } + types[i] = MAILBOX_LIST_PATH_TYPE_CONTROL; dirs[i++] = mailbox_list_get_path(box->list, box->name, MAILBOX_LIST_PATH_TYPE_CONTROL); + types[i] = MAILBOX_LIST_PATH_TYPE_INDEX; dirs[i++] = mailbox_list_get_path(box->list, box->name, MAILBOX_LIST_PATH_TYPE_INDEX); i_assert(i == N_ELEMENTS(dirs)); @@ -197,8 +197,7 @@ static int maildir_create_subdirs(struct mailbox *box) "stat(%s) failed: %m", path); break; } - is_mail_dir = i < N_ELEMENTS(subdirs); - if (maildir_create_path(box, path, is_mail_dir) < 0) + if (maildir_create_path(box, path, types[i], TRUE) < 0) break; } return i == N_ELEMENTS(dirs) ? 0 : -1; diff --git a/src/lib-storage/mail-namespace.c b/src/lib-storage/mail-namespace.c index 872ece1aa4..659e70a396 100644 --- a/src/lib-storage/mail-namespace.c +++ b/src/lib-storage/mail-namespace.c @@ -60,6 +60,7 @@ static void mail_namespace_free(struct mail_namespace *ns) static int namespace_add(struct mail_user *user, struct mail_namespace_settings *ns_set, + struct mail_namespace_settings *unexpanded_ns_set, const struct mail_storage_settings *mail_set, struct mail_namespace **ns_p, const char **error_r) { @@ -115,6 +116,7 @@ namespace_add(struct mail_user *user, ns_set->location = mail_set->mail_location; ns->set = ns_set; + ns->unexpanded_set = unexpanded_ns_set; ns->mail_set = mail_set; ns->prefix = i_strdup(ns_set->prefix); @@ -253,24 +255,29 @@ int mail_namespaces_init(struct mail_user *user, const char **error_r) { const struct mail_storage_settings *mail_set; struct mail_namespace_settings *const *ns_set; + struct mail_namespace_settings *const *unexpanded_ns_set; struct mail_namespace *namespaces, *ns, **ns_p; struct mail_namespace_settings *inbox_set; const char *error, *driver, *location_source; - unsigned int i, count; + unsigned int i, count, count2; i_assert(user->initialized); namespaces = NULL; ns_p = &namespaces; mail_set = mail_user_set_get_storage_set(user); - if (array_is_created(&user->set->namespaces)) + if (array_is_created(&user->set->namespaces)) { ns_set = array_get(&user->set->namespaces, &count); - else { - ns_set = NULL; + unexpanded_ns_set = + array_get(&user->unexpanded_set->namespaces, &count2); + i_assert(count == count2); + } else { + ns_set = unexpanded_ns_set = NULL; count = 0; } for (i = 0; i < count; i++) { - if (namespace_add(user, ns_set[i], mail_set, ns_p, error_r) < 0) + if (namespace_add(user, ns_set[i], unexpanded_ns_set[i], + mail_set, ns_p, error_r) < 0) return -1; ns_p = &(*ns_p)->next; } diff --git a/src/lib-storage/mail-namespace.h b/src/lib-storage/mail-namespace.h index 3faa828e12..994c8f6ccc 100644 --- a/src/lib-storage/mail-namespace.h +++ b/src/lib-storage/mail-namespace.h @@ -62,7 +62,7 @@ struct mail_namespace { /* FIXME: we should support multiple storages in one namespace */ struct mail_storage *storage; - const struct mail_namespace_settings *set; + const struct mail_namespace_settings *set, *unexpanded_set; const struct mail_storage_settings *mail_set; }; diff --git a/src/lib-storage/mailbox-list-private.h b/src/lib-storage/mailbox-list-private.h index 0a3d41fc29..a2603d5c25 100644 --- a/src/lib-storage/mailbox-list-private.h +++ b/src/lib-storage/mailbox-list-private.h @@ -140,9 +140,13 @@ void mailbox_lists_deinit(void); int mailbox_list_settings_parse(struct mail_user *user, const char *data, struct mailbox_list_settings *set_r, const char **error_r); +const char *mailbox_list_get_unexpanded_path(struct mailbox_list *list, + enum mailbox_list_path_type type); const char * mailbox_list_get_root_path(const struct mailbox_list_settings *set, enum mailbox_list_path_type type); +int mailbox_list_mkdir(struct mailbox_list *list, const char *path, + enum mailbox_list_path_type type); int mailbox_list_delete_index_control(struct mailbox_list *list, const char *name); diff --git a/src/lib-storage/mailbox-list.c b/src/lib-storage/mailbox-list.c index 9839ed161e..b1f61ef4d4 100644 --- a/src/lib-storage/mailbox-list.c +++ b/src/lib-storage/mailbox-list.c @@ -306,6 +306,37 @@ int mailbox_list_settings_parse(struct mail_user *user, const char *data, return 0; } +const char *mailbox_list_get_unexpanded_path(struct mailbox_list *list, + enum mailbox_list_path_type type) +{ + const struct mail_storage_settings *mail_set; + const char *location = list->ns->unexpanded_set->location; + struct mail_user *user = list->ns->user; + struct mailbox_list_settings set; + const char *p, *error; + + i_assert(*location == '0'); + location++; + + if (*location == '\0') { + mail_set = mail_user_set_get_driver_settings(user->set_info, + user->unexpanded_set, MAIL_STORAGE_SET_DRIVER_NAME); + i_assert(mail_set != NULL); + location = mail_set->mail_location; + i_assert(*location == '0'); + location++; + } + + /* type:settings */ + p = strchr(location, ':'); + if (p == NULL) + return ""; + + if (mailbox_list_settings_parse(user, p + 1, &set, &error) < 0) + return ""; + return mailbox_list_get_root_path(&set, type); +} + void mailbox_list_destroy(struct mailbox_list **_list) { struct mailbox_list *list = *_list; @@ -485,6 +516,129 @@ void mailbox_list_get_dir_permissions(struct mailbox_list *list, mode_r, gid_r, gid_origin_r); } +static int +mailbox_list_stat_parent(struct mailbox_list *list, const char *path, + const char **root_dir_r, struct stat *st_r) +{ + const char *p; + + while (stat(path, st_r) < 0) { + if (errno != ENOENT || strcmp(path, "/") == 0) { + mailbox_list_set_critical(list, "stat(%s) failed: %m", + path); + return -1; + } + p = strrchr(path, '/'); + if (p == NULL) + path = "/"; + else + path = t_strdup_until(path, p); + } + *root_dir_r = path; + return 0; +} + +static const char * +get_expanded_path(const char *unexpanded_start, const char *unexpanded_stop, + const char *expanded_full) +{ + const char *ret; + unsigned int i, slash_count = 0, slash2_count = 0; + + /* get the expanded path up to the same amount of '/' characters. + if there isn't the same amount of '/' characters, it means %variable + expansion added more of them and we can't handle this. */ + for (i = 0; unexpanded_start+i != unexpanded_stop; i++) { + if (unexpanded_start[i] == '/') + slash_count++; + } + for (; unexpanded_start[i] != '\0'; i++) { + if (unexpanded_start[i] == '/') + slash2_count++; + } + + for (i = 0; expanded_full[i] != '\0'; i++) { + if (expanded_full[i] == '/') { + if (slash_count == 0) + break; + slash_count--; + } + } + if (slash_count != 0) + return ""; + + ret = t_strndup(expanded_full, i); + for (; expanded_full[i] != '\0'; i++) { + if (expanded_full[i] == '/') { + if (slash2_count == 0) + return ""; + slash2_count--; + } + } + if (slash2_count != 0) + return ""; + return ret; +} + +int mailbox_list_mkdir(struct mailbox_list *list, const char *path, + enum mailbox_list_path_type type) +{ + const char *expanded, *unexpanded, *root_dir, *p, *origin; + struct stat st; + mode_t mode; + gid_t gid; + + mailbox_list_get_dir_permissions(list, NULL, &mode, &gid, &origin); + + /* get the directory path up to last %variable. for example + unexpanded path may be "/var/mail/%d/%2n/%n/Maildir", and we want + to get expanded="/var/mail/domain/nn" */ + unexpanded = mailbox_list_get_unexpanded_path(list, type); + p = strrchr(unexpanded, '%'); + if (p == NULL) + expanded = ""; + else { + while (p != unexpanded && *p != '/') p--; + if (p == unexpanded) + expanded = ""; + else { + expanded = mailbox_list_get_path(list, NULL, type); + expanded = get_expanded_path(unexpanded, p, expanded); + } + } + + if (*expanded != '\0') { + /* up to this directory get the permissions from the first + parent directory that exists, if it has setgid bit + enabled. */ + if (mailbox_list_stat_parent(list, expanded, + &root_dir, &st) < 0) + return -1; + if ((st.st_mode & S_ISGID) != 0 && root_dir != expanded) { + if (mkdir_parents_chgrp(expanded, st.st_mode, + (gid_t)-1, root_dir) < 0 && + errno != EEXIST) { + mailbox_list_set_critical(list, + "mkdir(%s) failed: %m", expanded); + return -1; + } + } + if (gid == (gid_t)-1 && (mode & S_ISGID) == 0) { + /* change the group for user directories */ + gid = getegid(); + } + } + + /* the rest of the directories exist only for one user. create them + with default directory permissions */ + if (mkdir_parents_chgrp(path, mode, gid, origin) < 0 && + errno != EEXIST) { + mailbox_list_set_critical(list, "mkdir(%s) failed: %m", path); + return -1; + } + return 0; +} + bool mailbox_list_is_valid_pattern(struct mailbox_list *list, const char *pattern) { -- 2.47.3