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; } }
static void imapc_list_sep_verify(struct imapc_mailbox_list *list) { const char *imapc_list_prefix = list->set->imapc_list_prefix; if (list->root_sep == '\0') { mailbox_list_set_critical(&list->list, "imapc: LIST didn't return hierarchy separator"); } else if (imapc_list_prefix[0] != '\0' && imapc_list_prefix[strlen(imapc_list_prefix)-1] == list->root_sep) { mailbox_list_set_critical(&list->list, "imapc_list_prefix must not end with hierarchy separator"); } }
static const char *next_line(struct mailbox_list *list, const char *path, struct istream *input, bool *failed_r, bool ignore_estale) { const char *line; *failed_r = FALSE; while ((line = i_stream_next_line(input)) == NULL) { switch (i_stream_read(input)) { case -1: if (input->stream_errno != 0 && (input->stream_errno != ESTALE || !ignore_estale)) { subswrite_set_syscall_error(list, "read()", path); *failed_r = TRUE; } return NULL; case -2: /* mailbox name too large */ mailbox_list_set_critical(list, "Subscription file %s contains lines longer " "than %u characters", path, (unsigned int)list->mailbox_name_max_length); *failed_r = TRUE; return NULL; } } return line; }
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 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 void subswrite_set_syscall_error(struct mailbox_list *list, const char *function, const char *path) { if (errno == EACCES && !list->mail_set->mail_debug) { mailbox_list_set_error(list, MAIL_ERROR_PERM, "No permission to modify subscriptions"); } else { mailbox_list_set_critical(list, "%s failed with subscription file %s: %m", function, path); } }
int mailbox_list_index_parse(struct mailbox_list *list, struct mail_index_view *view, bool force) { struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list); const struct mail_index_header *hdr; const char *error; hdr = mail_index_get_header(view); if (!force && hdr->log_file_seq == ilist->sync_log_file_seq && hdr->log_file_head_offset == ilist->sync_log_file_offset) { /* nothing changed */ return 0; } mailbox_list_index_reset(ilist); ilist->sync_log_file_seq = hdr->log_file_seq; ilist->sync_log_file_offset = hdr->log_file_head_offset; if (mailbox_list_index_parse_header(ilist, view) < 0) { mailbox_list_set_critical(list, "Corrupted mailbox list index header %s", ilist->path); if (ilist->has_backing_store) { mail_index_mark_corrupted(ilist->index); return -1; } } if (mailbox_list_index_parse_records(ilist, view, &error) < 0) { mailbox_list_set_critical(list, "Corrupted mailbox list index %s: %s", ilist->path, error); if (ilist->has_backing_store) { mail_index_mark_corrupted(ilist->index); return -1; } /* FIXME: find any missing mailboxes, add them and write the index back. */ } return 0; }
int mailbox_list_delete_mailbox_file(struct mailbox_list *list, const char *name, const char *path) { /* 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_MAILBOX_LIST_ERR_NOT_FOUND(list, 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 void imapc_storage_sep_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_mailbox_list *list = context; list->root_sep_pending = FALSE; if (reply->state == IMAPC_COMMAND_STATE_OK) imapc_list_sep_verify(list); else if (reply->state == IMAPC_COMMAND_STATE_NO) imapc_list_copy_error_from_reply(list, MAIL_ERROR_PARAMS, reply); else if (list->client->auth_failed) ; else if (!list->list.ns->user->deinitializing) { mailbox_list_set_critical(&list->list, "imapc: Command failed: %s", reply->text_full); } imapc_client_stop(list->client->client); }
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 void imapc_list_simple_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_simple_context *ctx = context; if (reply->state == IMAPC_COMMAND_STATE_OK) ctx->ret = 0; else if (reply->state == IMAPC_COMMAND_STATE_NO) { imapc_list_copy_error_from_reply(ctx->client->_list, MAIL_ERROR_PARAMS, reply); ctx->ret = -1; } else if (ctx->client->auth_failed) { ctx->ret = -1; } else { mailbox_list_set_critical(&ctx->client->_list->list, "imapc: Command failed: %s", reply->text_full); ctx->ret = -1; } imapc_client_stop(ctx->client->client); }
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); } }
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; }
int mailbox_list_delete_mailbox_nonrecursive(struct mailbox_list *list, const char *name, const char *path, bool rmdir_path) { DIR *dir; struct dirent *d; string_t *full_path; unsigned int dir_len; bool mailbox_dir, unlinked_something = FALSE; if (mailbox_list_check_root_delete(list, name, path) < 0) return -1; dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) { mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); } else { if (!mailbox_list_set_error_from_errno(list)) { mailbox_list_set_critical(list, "opendir(%s) failed: %m", path); } } return -1; } full_path = t_str_new(256); str_append(full_path, path); str_append_c(full_path, '/'); dir_len = str_len(full_path); for (errno = 0; (d = readdir(dir)) != NULL; errno = 0) { if (d->d_name[0] == '.') { /* skip . and .. */ if (d->d_name[1] == '\0') continue; if (d->d_name[1] == '.' && d->d_name[2] == '\0') continue; } mailbox_dir = list->v.is_internal_name != NULL && list->v.is_internal_name(list, d->d_name); str_truncate(full_path, dir_len); str_append(full_path, d->d_name); if (mailbox_dir) { if (mailbox_list_delete_trash(str_c(full_path)) < 0) { mailbox_list_set_critical(list, "unlink_directory(%s) failed: %m", str_c(full_path)); } else { unlinked_something = TRUE; } continue; } /* trying to unlink() a directory gives either EPERM or EISDIR (non-POSIX). it doesn't really work anywhere in practise, so don't bother stat()ing the file first */ if (unlink(str_c(full_path)) == 0) unlinked_something = TRUE; else if (errno != ENOENT && errno != EISDIR && errno != EPERM) { mailbox_list_set_critical(list, "unlink_directory(%s) failed: %m", str_c(full_path)); } } if (errno != 0) mailbox_list_set_critical(list, "readdir(%s) failed: %m", path); if (closedir(dir) < 0) { mailbox_list_set_critical(list, "closedir(%s) failed: %m", path); } if (rmdir_path) { if (rmdir(path) == 0) unlinked_something = TRUE; else if (errno != ENOENT && errno != ENOTEMPTY && errno != EEXIST) { mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path); return -1; } } if (!unlinked_something) { mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Mailbox has children, can't delete it"); return -1; } return 0; }
int mailbox_list_subscriptions_refresh(struct mailbox_list *src_list, struct mailbox_list *dest_list) { struct subsfile_list_context *subsfile_ctx; struct stat st; enum mailbox_list_path_type type; const char *path, *name; char sep; int ret; /* src_list is subscriptions=yes, dest_list is subscriptions=no (or the same as src_list) */ i_assert((src_list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0); if (dest_list->subscriptions == NULL) { sep = mail_namespace_get_sep(src_list->ns); dest_list->subscriptions = mailbox_tree_init(sep); } type = src_list->set.control_dir != NULL ? MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR; if (!mailbox_list_get_root_path(src_list, type, &path) || src_list->set.subscription_fname == NULL) { /* no subscriptions (e.g. pop3c) */ return 0; } path = t_strconcat(path, "/", src_list->set.subscription_fname, NULL); if (stat(path, &st) < 0) { if (errno == ENOENT) { /* no subscriptions */ mailbox_tree_clear(dest_list->subscriptions); dest_list->subscriptions_mtime = 0; return 0; } mailbox_list_set_critical(dest_list, "stat(%s) failed: %m", path); return -1; } if (st.st_mtime == dest_list->subscriptions_mtime && st.st_mtime < dest_list->subscriptions_read_time-1) { /* we're up to date */ return 0; } mailbox_tree_clear(dest_list->subscriptions); dest_list->subscriptions_read_time = ioloop_time; subsfile_ctx = subsfile_list_init(dest_list, path); if (subsfile_list_fstat(subsfile_ctx, &st) == 0) dest_list->subscriptions_mtime = st.st_mtime; while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN { T_BEGIN { ret = mailbox_list_subscription_fill_one(dest_list, src_list, name); } T_END; if (ret < 0) { i_warning("Subscriptions file %s: " "Removing invalid entry: %s", path, name); (void)subsfile_set_subscribed(src_list, path, mailbox_list_get_temp_prefix(src_list), name, FALSE); } } T_END; if (subsfile_list_deinit(&subsfile_ctx) < 0) { dest_list->subscriptions_mtime = (time_t)-1; return -1; } return 0; }
int fs_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) { struct stat st; const char *path; *flags_r = 0; if (*list->set.maildir_name != '\0') { /* maildir_name is set: the code is common for all storage types */ return list_is_maildir_mailbox(list, dir, fname, type, flags_r); } if (list->v.is_internal_name != NULL && list->v.is_internal_name(list, fname)) { /* skip internal dirs */ *flags_r |= MAILBOX_NOSELECT; return 0; } switch (type) { case MAILBOX_LIST_FILE_TYPE_DIR: if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) { *flags_r |= MAILBOX_NOSELECT; return 1; } break; case MAILBOX_LIST_FILE_TYPE_FILE: if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) { *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS; return 0; } break; default: break; } /* we've done all filtering we can before stat()ing */ path = t_strconcat(dir, "/", fname, NULL); if (stat(path, &st) < 0) { if (ENOTFOUND(errno)) { *flags_r |= MAILBOX_NONEXISTENT; return 0; } else if (ENOACCESS(errno)) { *flags_r |= MAILBOX_NOSELECT; return 1; } else { /* non-selectable. probably either access denied, or symlink destination not found. don't bother logging errors. */ mailbox_list_set_critical(list, "stat(%s) failed: %m", path); return -1; } } if (!S_ISDIR(st.st_mode)) { if (strncmp(fname, ".nfs", 4) == 0) { /* temporary NFS file */ *flags_r |= MAILBOX_NONEXISTENT; return 0; } if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) { *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS; return 0; } /* looks like a valid mailbox file */ if (is_inbox_file(list, path, fname) && strcmp(fname, "INBOX") != 0) { /* it's possible for INBOX to have child mailboxes as long as the inbox file itself isn't in <mail root>/INBOX */ } else { *flags_r |= MAILBOX_NOINFERIORS; } } else { if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) { *flags_r |= MAILBOX_NOSELECT | MAILBOX_CHILDREN; return 1; } } if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) { *flags_r |= STAT_GET_MARKED_FILE(st); } else if (list->v.is_internal_name == NULL) { /* link count < 2 can happen with filesystems that don't support link counts. we'll just ignore them for now.. */ if (st.st_nlink == 2) *flags_r |= MAILBOX_NOCHILDREN; else if (st.st_nlink > 2) *flags_r |= MAILBOX_CHILDREN; } return 1; }