Mercurial > dovecot > core-2.2
view src/lib-storage/list/mailbox-list-maildir-iter.c @ 21322:5ab8dc1a4a6f
global: Change string position/length from unsigned int to size_t
Mainly to avoid truncating >4GB strings, which might potentially cause
some security holes. Normally there are other limits, which prevent such
excessive strings from being created in the first place.
I'm sure this didn't find everything. Maybe everything could be found with
compiler warnings. -Wconversion kind of does it, but it gives way too many
unnecessary warnings.
These were mainly found with:
grep " = strlen"
egrep "unsigned int.*(size|len)"
author | Timo Sirainen <timo.sirainen@dovecot.fi> |
---|---|
date | Mon, 12 Dec 2016 07:19:55 +0200 |
parents | 0f22db71df7a |
children | 2e2563132d5f |
line wrap: on
line source
/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "ioloop.h" #include "unlink-directory.h" #include "unichar.h" #include "imap-match.h" #include "imap-utf7.h" #include "mailbox-tree.h" #include "mailbox-list-delete.h" #include "mailbox-list-subscriptions.h" #include "mailbox-list-maildir.h" #include <stdio.h> #include <dirent.h> #include <sys/stat.h> struct maildir_list_iterate_context { struct mailbox_list_iterate_context ctx; const char *dir; char prefix_char; struct mailbox_tree_context *tree_ctx; struct mailbox_tree_iterate_context *tree_iter; struct mailbox_info info; }; static void node_fix_parents(struct mailbox_node *node) { /* Fix parent nodes' children states. also if we happened to create any of the parents, we need to mark them nonexistent. */ node = node->parent; for (; node != NULL; node = node->parent) { if ((node->flags & MAILBOX_MATCHED) == 0) node->flags |= MAILBOX_NONEXISTENT; node->flags |= MAILBOX_CHILDREN; node->flags &= ~MAILBOX_NOCHILDREN; } } static void maildir_fill_parents(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, bool update_only, const char *vname) { struct mail_namespace *ns = ctx->ctx.list->ns; struct mailbox_node *node; const char *p; size_t vname_len = strlen(vname); bool created; char ns_sep = mail_namespace_get_sep(ns); while ((p = strrchr(vname, ns_sep)) != NULL) { vname = t_strdup_until(vname, p); if (imap_match(glob, vname) != IMAP_MATCH_YES) continue; if (ns->prefix_len > 0 && vname_len == ns->prefix_len-1 && strncmp(vname, ns->prefix, ns->prefix_len - 1) == 0 && vname[ns->prefix_len-1] == ns_sep) { /* don't return matches to namespace prefix itself */ continue; } created = FALSE; node = update_only ? mailbox_tree_lookup(ctx->tree_ctx, vname) : mailbox_tree_get(ctx->tree_ctx, vname, &created); if (node != NULL) { if (created) { /* we haven't yet seen this mailbox, but we might see it later */ node->flags = MAILBOX_NONEXISTENT; } if (!update_only) node->flags |= MAILBOX_MATCHED; node->flags |= MAILBOX_CHILDREN; node->flags &= ~MAILBOX_NOCHILDREN; node_fix_parents(node); } } } static void maildir_set_children(struct maildir_list_iterate_context *ctx, const char *vname) { struct mailbox_node *node; const char *p; char hierarchy_sep; hierarchy_sep = mail_namespace_get_sep(ctx->ctx.list->ns); /* mark the first existing parent as containing children */ while ((p = strrchr(vname, hierarchy_sep)) != NULL) { vname = t_strdup_until(vname, p); node = mailbox_tree_lookup(ctx->tree_ctx, vname); if (node != NULL) { node->flags &= ~MAILBOX_NOCHILDREN; node->flags |= MAILBOX_CHILDREN; break; } } } static int maildir_fill_inbox(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, const char *inbox_name, bool update_only) { struct mailbox_node *node; enum mailbox_info_flags flags; enum imap_match_result match; bool created; int ret; if ((ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0) { /* always show INBOX */ } else { /* INBOX may be Maildir root or completely elsewhere. show it only if it has already been created */ ret = mailbox_list_mailbox(ctx->ctx.list, "INBOX", &flags); if (ret < 0) return -1; if ((flags & MAILBOX_NONEXISTENT) != 0) update_only = TRUE; } if (update_only) { node = mailbox_tree_lookup(ctx->tree_ctx, inbox_name); if (node != NULL) node->flags &= ~MAILBOX_NONEXISTENT; return 0; } /* add the INBOX only if it matches the patterns */ match = imap_match(glob, inbox_name); if (match == IMAP_MATCH_PARENT) maildir_fill_parents(ctx, glob, FALSE, inbox_name); else if (match == IMAP_MATCH_YES) { node = mailbox_tree_get(ctx->tree_ctx, inbox_name, &created); if (created) node->flags = MAILBOX_NOCHILDREN; else node->flags &= ~MAILBOX_NONEXISTENT; node->flags |= MAILBOX_MATCHED; } return 0; } static bool maildir_get_type(const char *dir, const char *fname, enum mailbox_list_file_type *type_r, enum mailbox_info_flags *flags) { const char *path; struct stat st; path = *fname == '\0' ? dir : t_strdup_printf("%s/%s", dir, fname); if (stat(path, &st) < 0) { if (errno == ENOENT) { /* just deleted? */ *flags |= MAILBOX_NONEXISTENT; } else { *flags |= MAILBOX_NOSELECT; } return FALSE; } if (S_ISDIR(st.st_mode)) { *type_r = MAILBOX_LIST_FILE_TYPE_DIR; return TRUE; } else { if (strncmp(fname, ".nfs", 4) == 0) *flags |= MAILBOX_NONEXISTENT; else *flags |= MAILBOX_NOSELECT; return FALSE; } } int maildir_list_get_mailbox_flags(struct mailbox_list *list, const char *dir, const char *fname, enum mailbox_list_file_type type, enum mailbox_info_flags *flags_r) { *flags_r = 0; switch (type) { case MAILBOX_LIST_FILE_TYPE_DIR: case MAILBOX_LIST_FILE_TYPE_FILE: case MAILBOX_LIST_FILE_TYPE_OTHER: break; case MAILBOX_LIST_FILE_TYPE_UNKNOWN: case MAILBOX_LIST_FILE_TYPE_SYMLINK: /* need to check with stat() to be sure */ if (!list->mail_set->maildir_stat_dirs && *fname != '\0' && strcmp(list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0 && strncmp(fname, ".nfs", 4) != 0) { /* just assume it's a valid mailbox */ return 1; } if (!maildir_get_type(dir, fname, &type, flags_r)) return 0; break; } switch (type) { case MAILBOX_LIST_FILE_TYPE_DIR: if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) { *flags_r |= MAILBOX_NOSELECT; return 0; } break; case MAILBOX_LIST_FILE_TYPE_FILE: if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) { *flags_r |= MAILBOX_NOSELECT; return 0; } break; case MAILBOX_LIST_FILE_TYPE_OTHER: *flags_r |= MAILBOX_NOSELECT; return 0; case MAILBOX_LIST_FILE_TYPE_UNKNOWN: case MAILBOX_LIST_FILE_TYPE_SYMLINK: i_unreached(); } if (*fname != '\0') { /* this tells maildir storage code that it doesn't need to see if cur/ exists, because just the existence of .dir/ assumes that the mailbox exists. */ *flags_r |= MAILBOX_SELECT; } return 1; } static bool maildir_delete_trash_dir(struct maildir_list_iterate_context *ctx, const char *fname) { const char *path; struct stat st; if (fname[1] != ctx->prefix_char || ctx->prefix_char == '\0' || strcmp(fname+2, MAILBOX_LIST_MAILDIR_TRASH_DIR_NAME) != 0) return FALSE; /* this directory is in the middle of being deleted, or the process trying to delete it had died. delete it ourself if it's been there longer than one hour. */ path = t_strdup_printf("%s/%s", ctx->dir, fname); if (stat(path, &st) == 0 && st.st_mtime < ioloop_time - 3600) (void)mailbox_list_delete_trash(path); return TRUE; } static int maildir_fill_readdir_entry(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, const struct dirent *d, bool update_only) { struct mailbox_list *list = ctx->ctx.list; const char *fname, *storage_name, *vname; enum mailbox_info_flags flags; enum imap_match_result match; struct mailbox_node *node; bool created; int ret; fname = d->d_name; if (fname[0] == ctx->prefix_char) storage_name = fname + 1; else { if (ctx->prefix_char != '\0' || fname[0] == '.') return 0; storage_name = fname; } /* skip . and .. */ if (fname[0] == '.' && (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))) return 0; vname = mailbox_list_get_vname(list, storage_name); if (!uni_utf8_str_is_valid(vname)) { /* the storage_name is completely invalid, rename it to something more sensible. we could do this for all names that aren't valid mUTF-7, but that might lead to accidents in future when UTF-8 storage names are used */ const char *src = t_strdup_printf("%s/%s", ctx->dir, fname); string_t *destvname = t_str_new(128); string_t *dest = t_str_new(128); (void)uni_utf8_get_valid_data((const void *)fname, strlen(fname), destvname); str_append(dest, ctx->dir); str_append_c(dest, '/'); (void)imap_utf8_to_utf7(str_c(destvname), dest); if (rename(src, str_c(dest)) < 0 && errno != ENOENT) i_error("rename(%s, %s) failed: %m", src, str_c(dest)); /* just skip this in this iteration, we'll see it on the next list */ return 0; } /* make sure the pattern matches */ match = imap_match(glob, vname); if ((match & (IMAP_MATCH_YES | IMAP_MATCH_PARENT)) == 0) return 0; /* check if this is an actual mailbox */ if (maildir_delete_trash_dir(ctx, fname)) return 0; if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) { ret = mailbox_list_dirent_is_alias_symlink(list, ctx->dir, d); if (ret != 0) return ret < 0 ? -1 : 0; } T_BEGIN { ret = list->v.get_mailbox_flags(list, ctx->dir, fname, mailbox_list_get_file_type(d), &flags); } T_END; if (ret <= 0) return ret; /* we know the children flags ourself, so ignore if any of them were set. */ flags &= ~(MAILBOX_NOINFERIORS | MAILBOX_CHILDREN | MAILBOX_NOCHILDREN); if ((match & IMAP_MATCH_PARENT) != 0) maildir_fill_parents(ctx, glob, update_only, vname); else { created = FALSE; node = update_only ? mailbox_tree_lookup(ctx->tree_ctx, vname) : mailbox_tree_get(ctx->tree_ctx, vname, &created); if (node != NULL) { if (created) node->flags = MAILBOX_NOCHILDREN; else node->flags &= ~MAILBOX_NONEXISTENT; if (!update_only) node->flags |= MAILBOX_MATCHED; node->flags |= flags; node_fix_parents(node); } else { i_assert(update_only); maildir_set_children(ctx, vname); } } return 0; } static int maildir_fill_readdir(struct maildir_list_iterate_context *ctx, struct imap_match_glob *glob, bool update_only) { struct mailbox_list *list = ctx->ctx.list; struct mail_namespace *ns = list->ns; DIR *dirp; struct dirent *d; const char *vname; int ret = 0; dirp = opendir(ctx->dir); if (dirp == NULL) { if (errno == EACCES) { mailbox_list_set_critical(list, "%s", mail_error_eacces_msg("opendir", ctx->dir)); } else if (errno != ENOENT) { mailbox_list_set_critical(list, "opendir(%s) failed: %m", ctx->dir); return -1; } return 0; } while ((d = readdir(dirp)) != NULL) { T_BEGIN { ret = maildir_fill_readdir_entry(ctx, glob, d, update_only); } T_END; if (ret < 0) break; } if (closedir(dirp) < 0) { mailbox_list_set_critical(list, "readdir(%s) failed: %m", ctx->dir); return -1; } if (ret < 0) return -1; if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) { /* make sure INBOX is listed */ return maildir_fill_inbox(ctx, glob, "INBOX", update_only); } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) { /* show shared INBOX. */ vname = mailbox_list_get_vname(ns->list, "INBOX"); return maildir_fill_inbox(ctx, glob, vname, update_only); } else { return 0; } } struct mailbox_list_iterate_context * maildir_list_iter_init(struct mailbox_list *_list, const char *const *patterns, enum mailbox_list_iter_flags flags) { struct maildir_mailbox_list *list = (struct maildir_mailbox_list *)_list; struct maildir_list_iterate_context *ctx; pool_t pool; char ns_sep = mail_namespace_get_sep(_list->ns); int ret; pool = pool_alloconly_create("mailbox list maildir iter", 1024); ctx = p_new(pool, struct maildir_list_iterate_context, 1); ctx->ctx.pool = pool; ctx->ctx.list = _list; ctx->ctx.flags = flags; ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, ns_sep); array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5); ctx->tree_ctx = mailbox_tree_init(ns_sep); ctx->info.ns = _list->ns; ctx->prefix_char = strcmp(_list->name, MAILBOX_LIST_NAME_IMAPDIR) == 0 ? '\0' : list->sep; ctx->dir = _list->set.root_dir; if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) { /* Listing only subscribed mailboxes. Flags are set later if needed. */ bool default_nonexistent = (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0; mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree_ctx, default_nonexistent); } if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 || (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0) { /* Add/update mailbox list with flags */ bool update_only = (flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0; T_BEGIN { ret = maildir_fill_readdir(ctx, ctx->ctx.glob, update_only); } T_END; if (ret < 0) { ctx->ctx.failed = TRUE; return &ctx->ctx; } } ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree_ctx, NULL, MAILBOX_MATCHED); return &ctx->ctx; } int maildir_list_iter_deinit(struct mailbox_list_iterate_context *_ctx) { struct maildir_list_iterate_context *ctx = (struct maildir_list_iterate_context *)_ctx; int ret = _ctx->failed ? -1 : 0; if (ctx->tree_iter != NULL) mailbox_tree_iterate_deinit(&ctx->tree_iter); mailbox_tree_deinit(&ctx->tree_ctx); pool_unref(&ctx->ctx.pool); return ret; } const struct mailbox_info * maildir_list_iter_next(struct mailbox_list_iterate_context *_ctx) { struct maildir_list_iterate_context *ctx = (struct maildir_list_iterate_context *)_ctx; struct mailbox_node *node; if (_ctx->failed) return NULL; node = mailbox_tree_iterate_next(ctx->tree_iter, &ctx->info.vname); if (node == NULL) return NULL; ctx->info.flags = node->flags; if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0 && (_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) { /* we're listing all mailboxes but we want to know \Subscribed flags */ mailbox_list_set_subscription_flags(_ctx->list, ctx->info.vname, &ctx->info.flags); } return &ctx->info; }