static void mailbox_list_try_delete(struct mailbox_list *list, const char *name, enum mailbox_list_path_type type) { const char *mailbox_path, *path; mailbox_path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); path = mailbox_list_get_path(list, name, type); if (path == NULL || *path == '\0' || strcmp(path, mailbox_path) == 0) return; if (*list->set.maildir_name == '\0' && (list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) { /* this directory may contain also child mailboxes' data. we don't want to delete that. */ bool rmdir_path = *list->set.maildir_name != '\0'; if (mailbox_list_delete_mailbox_nonrecursive(list, name, path, rmdir_path) < 0) return; } else { if (mailbox_list_delete_trash(path) < 0 && errno != ENOENT && errno != ENOTEMPTY) { mailbox_list_set_critical(list, "unlink_directory(%s) failed: %m", path); } } /* avoid leaving empty directories lying around */ mailbox_list_delete_until_root(list, path, type); }
static int mbox_storage_create(struct mail_storage *_storage, struct mail_namespace *ns, const char **error_r) { struct mbox_storage *storage = (struct mbox_storage *)_storage; struct stat st; const char *dir; if (master_service_get_client_limit(master_service) > 1) { /* we can't handle locking related problems. */ *error_r = "mbox requires client_limit=1 for service"; return -1; } storage->set = mail_storage_get_driver_settings(_storage); dir = mailbox_list_get_path(ns->list, NULL, MAILBOX_LIST_PATH_TYPE_INDEX); if (*dir != '\0') { _storage->temp_path_prefix = p_strconcat(_storage->pool, dir, "/", mailbox_list_get_temp_prefix(ns->list), NULL); } if (stat(ns->list->set.root_dir, &st) == 0 && !S_ISDIR(st.st_mode)) { *error_r = t_strdup_printf( "mbox root directory can't be a file: %s " "(http://wiki.dovecot.org/MailLocation/Mbox)", ns->list->set.root_dir); return -1; } return 0; }
static int mbox_mailbox_open_existing(struct mbox_mailbox *mbox) { struct mailbox *box = &mbox->box; const char *rootdir; bool move_to_memory; if (access(box->path, R_OK|W_OK) < 0) { if (errno != EACCES) { mbox_set_syscall_error(mbox, "access()"); return -1; } mbox->box.backend_readonly = TRUE; } move_to_memory = want_memory_indexes(mbox->storage, box->path); if (box->inbox_any || strcmp(box->name, "INBOX") == 0) { /* if INBOX isn't under the root directory, it's probably in /var/mail and we want to allow privileged dotlocking */ rootdir = mailbox_list_get_path(box->list, NULL, MAILBOX_LIST_PATH_TYPE_DIR); if (strncmp(box->path, rootdir, strlen(rootdir)) != 0) mbox->mbox_privileged_locking = TRUE; } if ((box->flags & MAILBOX_FLAG_KEEP_LOCKED) != 0) { if (mbox_lock(mbox, F_WRLCK, &mbox->mbox_global_lock_id) <= 0) return -1; if (mbox->mbox_dotlock != NULL) { mbox->keep_lock_to = timeout_add(MBOX_LOCK_TOUCH_MSECS, mbox_lock_touch_timeout, mbox); } } return index_storage_mailbox_open(box, move_to_memory); }
static int create_inbox(struct mailbox *box) { const char *inbox_path; int fd; inbox_path = mailbox_list_get_path(box->list, "INBOX", MAILBOX_LIST_PATH_TYPE_MAILBOX); fd = open(inbox_path, O_RDWR | O_CREAT | O_EXCL, 0660); if (fd == -1 && errno == EACCES) { /* try again with increased privileges */ (void)restrict_access_use_priv_gid(); fd = open(inbox_path, O_RDWR | O_CREAT | O_EXCL, 0660); restrict_access_drop_priv_gid(); } if (fd != -1) { (void)close(fd); return 0; } else if (errno == EACCES) { mail_storage_set_critical(box->storage, "%s", mail_error_create_eacces_msg("open", inbox_path)); return -1; } else if (errno == EEXIST) { mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS, "Mailbox already exists"); return -1; } else { mail_storage_set_critical(box->storage, "open(%s, O_CREAT) failed: %m", inbox_path); return -1; } }
int mailbox_list_delete_symlink_default(struct mailbox_list *list, const char *name) { const char *path; int ret; ret = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR, &path); if (ret < 0) return -1; i_assert(ret > 0); if (unlink(path) == 0) return 0; if (errno == ENOENT) { mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAILBOX_LIST_ERR_NOT_FOUND(list, name)); } else if (errno == EISDIR || errno == EPERM) { /* Solaris */ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Mailbox isn't a symlink"); } else { mailbox_list_set_critical(list, "unlink(%s) failed: %m", path); } return -1; }
static const char *cydir_mail_get_path(struct mail *mail) { const char *dir; dir = mailbox_list_get_path(mail->box->list, mail->box->name, MAILBOX_LIST_PATH_TYPE_MAILBOX); return t_strdup_printf("%s/%u.", dir, mail->uid); }
static const char * acl_backend_vfile_get_local_dir(struct acl_backend *backend, const char *name, const char *vname) { struct mail_namespace *ns = mailbox_list_get_namespace(backend->list); struct mailbox_list *list = ns->list; struct mail_storage *storage; enum mailbox_list_path_type type; const char *dir, *inbox; if (*name == '\0') name = NULL; /* ACL files are very important. try to keep them among the main mail files. that's not possible though with a) if the mailbox is a file or b) if the mailbox path doesn't point to filesystem. */ if (mailbox_list_get_storage(&list, vname, &storage) < 0) return NULL; i_assert(list == ns->list); type = mail_storage_is_mailbox_file(storage) || (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NO_ROOT) != 0 ? MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_MAILBOX; if (name == NULL) { if (!mailbox_list_get_root_path(list, type, &dir)) return NULL; } else { if (mailbox_list_get_path(list, name, type, &dir) <= 0) return NULL; } /* verify that the directory isn't same as INBOX's directory. this is mainly for Maildir. */ if (name == NULL && mailbox_list_get_path(list, "INBOX", MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox) > 0 && strcmp(inbox, dir) == 0) { /* can't have default ACLs with this setup */ return NULL; } return dir; }
static bool acl_backend_vfile_has_acl(struct acl_backend *_backend, const char *name) { struct acl_backend_vfile *backend = (struct acl_backend_vfile *)_backend; struct acl_backend_vfile_validity *old_validity, new_validity; const char *path, *local_path, *global_path, *dir, *vname = ""; const char *error; int ret; old_validity = acl_cache_get_validity(_backend->cache, name); if (old_validity != NULL) new_validity = *old_validity; else memset(&new_validity, 0, sizeof(new_validity)); /* See if the mailbox exists. If we wanted recursive lookups we could skip this, but at least for now we assume that if an existing mailbox has no ACL it's equivalent to default ACLs. */ if (mailbox_list_get_path(_backend->list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) <= 0) ret = -1; else { ret = acl_backend_vfile_exists(backend, path, &new_validity.mailbox_validity); } if (ret == 0 && (*name == '\0' || mailbox_list_is_valid_name(_backend->list, name, &error))) { vname = *name == '\0' ? "" : mailbox_list_get_vname(_backend->list, name); dir = acl_backend_vfile_get_local_dir(_backend, name, vname); if (dir != NULL) { local_path = t_strconcat(dir, "/", name, NULL); ret = acl_backend_vfile_exists(backend, local_path, &new_validity.local_validity); } } if (ret == 0 && backend->global_path != NULL) { if (_backend->global_file != NULL) { ret = acl_global_file_refresh(_backend->global_file); if (ret == 0 && acl_global_file_have_any(_backend->global_file, vname)) ret = 1; } else { global_path = t_strconcat(backend->global_path, "/", name, NULL); ret = acl_backend_vfile_exists(backend, global_path, &new_validity.global_validity); } } acl_cache_set_validity(_backend->cache, name, &new_validity); return ret > 0; }
static int rename_dir(struct mailbox_list *oldlist, const char *oldname, struct mailbox_list *newlist, const char *newname, enum mailbox_list_path_type type) { const char *oldpath, *newpath; if (mailbox_list_get_path(oldlist, oldname, type, &oldpath) <= 0 || mailbox_list_get_path(newlist, newname, type, &newpath) <= 0) return 0; if (strcmp(oldpath, newpath) == 0) return 0; if (rename(oldpath, newpath) < 0 && errno != ENOENT) { mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); return -1; } return 0; }
static bool is_inbox_file(struct mailbox_list *list, const char *path, const char *fname) { const char *inbox_path; if (strcasecmp(fname, "INBOX") != 0) return FALSE; if (mailbox_list_get_path(list, "INBOX", MAILBOX_LIST_PATH_TYPE_MAILBOX, &inbox_path) <= 0) i_unreached(); return strcmp(inbox_path, path) == 0; }
static const char *acl_list_get_root_dir(struct acl_backend_vfile *backend) { struct mail_storage *storage; const char *rootdir, *maildir; rootdir = mailbox_list_get_path(backend->backend.list, NULL, MAILBOX_LIST_PATH_TYPE_DIR); storage = mailbox_list_get_namespace(backend->backend.list)->storage; if (mail_storage_is_mailbox_file(storage)) { maildir = mailbox_list_get_path(backend->backend.list, NULL, MAILBOX_LIST_PATH_TYPE_MAILBOX); if (strcmp(maildir, rootdir) == 0) { /* dovecot-acl-list would show up as a mailbox if we created it to root dir. since we don't really have any other good alternatives, place it to control dir */ rootdir = mailbox_list_get_path(backend->backend.list, NULL, MAILBOX_LIST_PATH_TYPE_CONTROL); } } return rootdir; }
static int imapc_list_get_path(struct mailbox_list *_list, const char *name, enum mailbox_list_path_type type, const char **path_r) { struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list; struct mailbox_list *fs_list = imapc_list_get_fs(list); const char *fs_name; if (fs_list != NULL) { fs_name = imapc_list_get_fs_name(list, name); return mailbox_list_get_path(fs_list, fs_name, type, path_r); } else { *path_r = NULL; return 0; } }
static int mailbox_list_check_root_delete(struct mailbox_list *list, const char *name, const char *path) { const char *root_dir; root_dir = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_DIR); if (strcmp(root_dir, path) != 0) return 0; if (strcmp(name, "INBOX") == 0 && (list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) { mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "INBOX can't be deleted."); return -1; } mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Mail storage root can't be deleted."); return -1; }
static int maildir_list_delete_dir(struct mailbox_list *list, const char *name) { const char *path; struct stat st; /* with maildir++ there aren't any non-selectable mailboxes. we'll always fail. */ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR, &path) <= 0) i_unreached(); if (stat(path, &st) == 0) { mailbox_list_set_error(list, MAIL_ERROR_EXISTS, "Mailbox exists"); } else if (errno == ENOENT || errno == ENOTDIR) { mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); } else { mailbox_list_set_critical(list, "stat(%s) failed: %m", path); } return -1; }
void mailbox_list_delete_until_root(struct mailbox_list *list, const char *path, enum mailbox_list_path_type type) { const char *root_dir, *p; unsigned int len; root_dir = mailbox_list_get_path(list, NULL, type); if (strncmp(path, root_dir, strlen(root_dir)) != 0) { /* mbox workaround: name=child/box, root_dir=mail/.imap/, path=mail/child/.imap/box. we'll want to try to delete the .imap/ part, but no further. */ len = strlen(path); while (len > 0 && path[len-1] != '/') len--; if (len == 0) return; len--; while (len > 0 && path[len-1] != '/') len--; if (len == 0) return; root_dir = t_strndup(path, len-1); } while (strcmp(path, root_dir) != 0) { if (rmdir(path) < 0 && errno != ENOENT) { if (errno == ENOTEMPTY || errno == EEXIST) return; mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path); return; } p = strrchr(path, '/'); if (p == NULL) break; path = t_strdup_until(path, p); } }
static int maildir_list_delete_mailbox(struct mailbox_list *list, const char *name) { const char *path; int ret; if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) { ret = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path); if (ret < 0) return -1; i_assert(ret > 0); ret = mailbox_list_delete_mailbox_file(list, name, path); } else { ret = maildir_list_delete_maildir(list, name); } if (ret == 0 || (list->props & MAILBOX_LIST_PROP_AUTOCREATE_DIRS) != 0) mailbox_list_delete_finish(list, name); return ret; }
int mailbox_list_delete_mailbox_file(struct mailbox_list *list, const char *name) { const char *path; path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); /* we can simply unlink() the file */ if (unlink(path) == 0) return 0; else if (ENOTFOUND(errno)) { mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); return -1; } else { if (!mailbox_list_set_error_from_errno(list)) { mailbox_list_set_critical(list, "unlink(%s) failed: %m", path); } return -1; } }
static int maildir_list_delete_maildir(struct mailbox_list *list, const char *name) { const char *path, *trash_dir; int ret = 0; trash_dir = mailbox_list_maildir_get_trash_dir(list); ret = mailbox_list_delete_maildir_via_trash(list, name, trash_dir); if (ret < 0) return -1; if (ret == 0) { /* we could actually use just unlink_directory() but error handling is easier this way :) */ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) <= 0) i_unreached(); if (mailbox_list_delete_mailbox_nonrecursive(list, name, path, TRUE) < 0) return -1; } return 0; }
int mailbox_list_delete_maildir_via_trash(struct mailbox_list *list, const char *name, const char *trash_dir) { const char *src, *trash_dest; unsigned int count; src = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); if (mailbox_list_check_root_delete(list, name, src) < 0) return -1; /* rename the mailbox dir to trash dir, which atomically marks it as being deleted. */ count = 0; trash_dest = trash_dir; for (; rename(src, trash_dest) < 0; count++) { if (ENOTFOUND(errno)) { if (trash_dest != trash_dir && count < 5) { /* either the source was just deleted or the trash dir was deleted. */ trash_dest = trash_dir; continue; } mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); return -1; } if (errno == EXDEV) { /* can't do this the fast way */ return 0; } if (!EDESTDIREXISTS(errno)) { if (mailbox_list_set_error_from_errno(list)) return -1; mailbox_list_set_critical(list, "rename(%s, %s) failed: %m", src, trash_dest); return -1; } /* trash dir already exists. the reasons for this are: a) another process is in the middle of deleting it b) previous process crashed and didn't delete it c) NFS: mailbox was recently deleted, but some connection still has that mailbox open. the directory contains .nfs* files that can't be deleted until the mailbox is closed. Because of c) we'll first try to rename the mailbox under the trash directory and only later try to delete the entire trash directory. */ if (trash_dir == trash_dest) { trash_dest = t_strconcat(trash_dir, "/", unique_fname(), NULL); } else if (mailbox_list_delete_trash(trash_dest) < 0 && (errno != ENOTEMPTY || count >= 5)) { mailbox_list_set_critical(list, "unlink_directory(%s) failed: %m", trash_dest); return -1; } } if (mailbox_list_delete_trash(trash_dir) < 0 && errno != ENOTEMPTY && errno != EBUSY) { mailbox_list_set_critical(list, "unlink_directory(%s) failed: %m", trash_dir); /* it's already renamed to trash dir, which means it's deleted as far as the client is concerned. Report success. */ } return 1; }
static int maildir_rename_children(struct mailbox_list *oldlist, const char *oldname, struct mailbox_list *newlist, const char *newname) { struct mailbox_list_iterate_context *iter; const struct mailbox_info *info; ARRAY(const char *) names_arr; const char *pattern, *oldpath, *newpath, *old_childname, *new_childname; const char *const *names, *old_vname, *new_vname; unsigned int i, count, old_vnamelen; pool_t pool; char old_ns_sep; int ret; ret = 0; /* first get the list of the children and save them to memory, because we can't rely on readdir() not skipping files while the directory is being modified. this doesn't protect against modifications by other processes though. */ pool = pool_alloconly_create("Maildir++ children list", 1024); i_array_init(&names_arr, 64); old_vname = mailbox_list_get_vname(oldlist, oldname); old_vnamelen = strlen(old_vname); new_vname = mailbox_list_get_vname(newlist, newname); old_ns_sep = mail_namespace_get_sep(oldlist->ns); pattern = t_strdup_printf("%s%c*", old_vname, old_ns_sep); iter = mailbox_list_iter_init(oldlist, pattern, MAILBOX_LIST_ITER_RETURN_NO_FLAGS | MAILBOX_LIST_ITER_RAW_LIST); while ((info = mailbox_list_iter_next(iter)) != NULL) { const char *name; /* verify that the prefix matches, otherwise we could have problems with mailbox names containing '%' and '*' chars */ if (strncmp(info->vname, old_vname, old_vnamelen) == 0 && info->vname[old_vnamelen] == old_ns_sep) { name = p_strdup(pool, info->vname + old_vnamelen); array_append(&names_arr, &name, 1); } } if (mailbox_list_iter_deinit(&iter) < 0) { ret = -1; names = NULL; count = 0; } else { names = array_get(&names_arr, &count); } for (i = 0; i < count; i++) { old_childname = mailbox_list_get_storage_name(oldlist, t_strconcat(old_vname, names[i], NULL)); if (strcmp(old_childname, new_vname) == 0) { /* When doing RENAME "a" "a.b" we see "a.b" here. We don't want to rename it anymore to "a.b.b". */ continue; } new_childname = mailbox_list_get_storage_name(newlist, t_strconcat(new_vname, names[i], NULL)); if (mailbox_list_get_path(oldlist, old_childname, MAILBOX_LIST_PATH_TYPE_MAILBOX, &oldpath) <= 0 || mailbox_list_get_path(newlist, new_childname, MAILBOX_LIST_PATH_TYPE_MAILBOX, &newpath) <= 0) i_unreached(); /* FIXME: it's possible to merge two mailboxes if either one of them doesn't have existing root mailbox. We could check this but I'm not sure if it's worth it. It could be even considered as a feature. Anyway, the bug with merging is that if both mailboxes have identically named child mailbox they conflict. Just ignore those and leave them under the old mailbox. */ if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno)) ret = 1; else { mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); ret = -1; break; } (void)rename_dir(oldlist, old_childname, newlist, new_childname, MAILBOX_LIST_PATH_TYPE_CONTROL); (void)rename_dir(oldlist, old_childname, newlist, new_childname, MAILBOX_LIST_PATH_TYPE_INDEX); } array_free(&names_arr); pool_unref(&pool); return ret; }
static int maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, struct mailbox_list *newlist, const char *newname) { const char *oldpath, *newpath, *root_path; int ret; bool found; /* NOTE: it's possible to rename a nonexistent mailbox which has children. In that case we should ignore the rename() error. */ if (mailbox_list_get_path(oldlist, oldname, MAILBOX_LIST_PATH_TYPE_MAILBOX, &oldpath) <= 0 || mailbox_list_get_path(newlist, newname, MAILBOX_LIST_PATH_TYPE_MAILBOX, &newpath) <= 0) i_unreached(); root_path = mailbox_list_get_root_forced(oldlist, MAILBOX_LIST_PATH_TYPE_MAILBOX); if (strcmp(oldpath, root_path) == 0) { /* most likely INBOX */ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE, t_strdup_printf("Renaming %s isn't supported.", oldname)); return -1; } /* if we're renaming under another mailbox, require its permissions to be same as ours. */ if (strchr(newname, mailbox_list_get_hierarchy_sep(newlist)) != NULL) { struct mailbox_permissions old_perm, new_perm; mailbox_list_get_permissions(oldlist, oldname, &old_perm); mailbox_list_get_permissions(newlist, newname, &new_perm); if ((new_perm.file_create_mode != old_perm.file_create_mode || new_perm.dir_create_mode != old_perm.dir_create_mode || new_perm.file_create_gid != old_perm.file_create_gid)) { mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE, "Renaming not supported across conflicting " "directory permissions"); return -1; } } ret = rename(oldpath, newpath); if (ret == 0 || errno == ENOENT) { (void)rename_dir(oldlist, oldname, newlist, newname, MAILBOX_LIST_PATH_TYPE_CONTROL); (void)rename_dir(oldlist, oldname, newlist, newname, MAILBOX_LIST_PATH_TYPE_INDEX); found = ret == 0; T_BEGIN { ret = maildir_rename_children(oldlist, oldname, newlist, newname); } T_END; if (ret < 0) return -1; if (!found && ret == 0) { mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname)); return -1; } return 0; }