static int maildir_keywords_commit(struct maildir_keywords *mk) { const struct mailbox_permissions *perm; struct dotlock *dotlock; const char *lock_path; mode_t old_mask; int i, fd; mk->synced = FALSE; if (!mk->changed || mk->mbox == NULL) return 0; lock_path = t_strconcat(mk->path, ".lock", NULL); i_unlink_if_exists(lock_path); perm = mailbox_get_permissions(&mk->mbox->box); for (i = 0;; i++) { /* we could just create the temp file directly, but doing it this ways avoids potential problems with overwriting contents in malicious symlinks */ old_mask = umask(0777 & ~perm->file_create_mode); fd = file_dotlock_open(&mk->dotlock_settings, mk->path, DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock); umask(old_mask); if (fd != -1) break; if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) { mail_storage_set_critical(mk->storage, "file_dotlock_open(%s) failed: %m", mk->path); return -1; } /* the control dir doesn't exist. create it unless the whole mailbox was just deleted. */ if (!maildir_set_deleted(&mk->mbox->box)) return -1; } if (maildir_keywords_write_fd(mk, lock_path, fd) < 0) { file_dotlock_delete(&dotlock); return -1; } if (file_dotlock_replace(&dotlock, 0) < 0) { mail_storage_set_critical(mk->storage, "file_dotlock_replace(%s) failed: %m", mk->path); return -1; } mk->changed = FALSE; return 0; }
static int squat_uidlist_lock(struct squat_uidlist *uidlist) { int ret; for (;;) { i_assert(uidlist->fd != -1); i_assert(uidlist->file_lock == NULL); i_assert(uidlist->dotlock == NULL); if (uidlist->trie->lock_method != FILE_LOCK_METHOD_DOTLOCK) { ret = file_wait_lock(uidlist->fd, uidlist->path, F_WRLCK, uidlist->trie->lock_method, SQUAT_TRIE_LOCK_TIMEOUT, &uidlist->file_lock); } else { ret = file_dotlock_create(&uidlist->trie->dotlock_set, uidlist->path, 0, &uidlist->dotlock); } if (ret == 0) { i_error("squat uidlist %s: Locking timed out", uidlist->path); return 0; } if (ret < 0) return -1; ret = squat_uidlist_is_file_stale(uidlist); if (ret == 0) break; if (uidlist->file_lock != NULL) file_unlock(&uidlist->file_lock); else file_dotlock_delete(&uidlist->dotlock); if (ret < 0) return -1; squat_uidlist_close(uidlist); uidlist->fd = squat_trie_create_fd(uidlist->trie, uidlist->path, 0); if (uidlist->fd == -1) return -1; } return 1; }
static void squat_uidlist_close(struct squat_uidlist *uidlist) { i_assert(!uidlist->building); squat_uidlist_unmap(uidlist); if (uidlist->file_cache != NULL) file_cache_free(&uidlist->file_cache); if (uidlist->file_lock != NULL) file_lock_free(&uidlist->file_lock); if (uidlist->dotlock != NULL) file_dotlock_delete(&uidlist->dotlock); if (uidlist->fd != -1) { if (close(uidlist->fd) < 0) i_error("close(%s) failed: %m", uidlist->path); uidlist->fd = -1; } uidlist->corrupted = FALSE; }
int squat_uidlist_build_init(struct squat_uidlist *uidlist, struct squat_uidlist_build_context **ctx_r) { struct squat_uidlist_build_context *ctx; int ret; i_assert(!uidlist->building); ret = squat_uidlist_open_or_create(uidlist); if (ret == 0 && lseek(uidlist->fd, uidlist->hdr.used_file_size, SEEK_SET) < 0) { i_error("lseek(%s) failed: %m", uidlist->path); ret = -1; } if (ret < 0) { if (uidlist->file_lock != NULL) file_unlock(&uidlist->file_lock); if (uidlist->dotlock != NULL) file_dotlock_delete(&uidlist->dotlock); return -1; } ctx = i_new(struct squat_uidlist_build_context, 1); ctx->uidlist = uidlist; ctx->output = o_stream_create_fd(uidlist->fd, 0); if (ctx->output->offset == 0) { struct squat_uidlist_file_header hdr; memset(&hdr, 0, sizeof(hdr)); o_stream_nsend(ctx->output, &hdr, sizeof(hdr)); } o_stream_cork(ctx->output); i_array_init(&ctx->lists, 10240); i_array_init(&ctx->block_offsets, 128); i_array_init(&ctx->block_end_indexes, 128); ctx->list_start_idx = uidlist->hdr.count; ctx->build_hdr = uidlist->hdr; uidlist->building = TRUE; *ctx_r = ctx; return 0; }
int mail_transaction_log_file_create(struct mail_transaction_log_file *file, bool reset) { struct mail_index *index = file->log->index; struct dotlock_settings new_dotlock_set; struct dotlock *dotlock; mode_t old_mask; int fd, ret; i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); if (file->log->index->readonly) { mail_index_set_error(index, "Can't create log file %s: Index is read-only", file->filepath); return -1; } mail_transaction_log_get_dotlock_set(file->log, &new_dotlock_set); new_dotlock_set.lock_suffix = LOG_NEW_DOTLOCK_SUFFIX; /* With dotlocking we might already have path.lock created, so this filename has to be different. */ old_mask = umask(index->mode ^ 0666); fd = file_dotlock_open(&new_dotlock_set, file->filepath, 0, &dotlock); umask(old_mask); if (fd == -1) { log_file_set_syscall_error(file, "file_dotlock_open()"); return -1; } mail_index_fchown(index, fd, file_dotlock_get_lock_path(dotlock)); /* either fd gets used or the dotlock gets deleted and returned fd is for the existing file */ ret = mail_transaction_log_file_create2(file, fd, reset, &dotlock); if (ret < 0) { if (dotlock != NULL) file_dotlock_delete(&dotlock); return -1; } return ret; }
static int mail_transaction_log_file_undotlock(struct mail_transaction_log_file *file) { int ret; if (--file->log->dotlock_count > 0) return 0; ret = file_dotlock_delete(&file->log->dotlock); if (ret < 0) { log_file_set_syscall_error(file, "file_dotlock_delete()"); return -1; } if (ret == 0) { mail_index_set_error(file->log->index, "Dotlock was lost for transaction log file %s", file->filepath); return -1; } return 0; }
static int ATTR_NULL(2) ATTR_NOWARN_UNUSED_RESULT mbox_dotlock_privileged_op(struct mbox_mailbox *mbox, struct dotlock_settings *set, enum mbox_dotlock_op op) { const char *box_path, *dir, *fname; int ret = -1, orig_dir_fd, orig_errno; orig_dir_fd = open(".", O_RDONLY); if (orig_dir_fd == -1) { mailbox_set_critical(&mbox->box, "open(.) failed: %m"); return -1; } /* allow dotlocks to be created only for files we can read while we're unprivileged. to make sure there are no race conditions we first have to chdir to the mbox file's directory and then use relative paths. unless this is done, users could: - create *.lock files to any directory writable by the privileged group - DoS other users by dotlocking their mailboxes infinitely */ box_path = mailbox_get_path(&mbox->box); fname = strrchr(box_path, '/'); if (fname == NULL) { /* already relative */ fname = box_path; } else { dir = t_strdup_until(box_path, fname); if (chdir(dir) < 0) { mailbox_set_critical(&mbox->box, "chdir(%s) failed: %m", dir); i_close_fd(&orig_dir_fd); return -1; } fname++; } if (op == MBOX_DOTLOCK_OP_LOCK) { if (access(fname, R_OK) < 0) { mailbox_set_critical(&mbox->box, "access(%s) failed: %m", box_path); i_close_fd(&orig_dir_fd); return -1; } } if (restrict_access_use_priv_gid() < 0) { i_close_fd(&orig_dir_fd); return -1; } switch (op) { case MBOX_DOTLOCK_OP_LOCK: /* we're now privileged - avoid doing as much as possible */ ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock); if (ret > 0) mbox->mbox_used_privileges = TRUE; else if (ret < 0 && errno == EACCES) { const char *errmsg = eacces_error_get_creating("file_dotlock_create", fname); mailbox_set_critical(&mbox->box, "%s", errmsg); } else { mbox_set_syscall_error(mbox, "file_dotlock_create()"); } break; case MBOX_DOTLOCK_OP_UNLOCK: /* we're now privileged - avoid doing as much as possible */ ret = file_dotlock_delete(&mbox->mbox_dotlock); if (ret < 0) mbox_set_syscall_error(mbox, "file_dotlock_delete()"); mbox->mbox_used_privileges = FALSE; break; case MBOX_DOTLOCK_OP_TOUCH: ret = file_dotlock_touch(mbox->mbox_dotlock); if (ret < 0) mbox_set_syscall_error(mbox, "file_dotlock_touch()"); break; } orig_errno = errno; restrict_access_drop_priv_gid(); if (fchdir(orig_dir_fd) < 0) { mailbox_set_critical(&mbox->box, "fchdir() failed: %m"); } i_close_fd(&orig_dir_fd); errno = orig_errno; return ret; }
int subsfile_set_subscribed(struct mailbox_list *list, const char *path, const char *temp_prefix, const char *name, bool set) { const struct mail_storage_settings *mail_set = list->mail_set; struct dotlock_settings dotlock_set; struct dotlock *dotlock; struct mailbox_permissions perm; const char *line, *dir, *fname, *escaped_name; struct istream *input = NULL; struct ostream *output; int fd_in, fd_out; enum mailbox_list_path_type type; bool found, changed = FALSE, failed = FALSE; unsigned int version = 2; if (strcasecmp(name, "INBOX") == 0) name = "INBOX"; memset(&dotlock_set, 0, sizeof(dotlock_set)); dotlock_set.use_excl_lock = mail_set->dotlock_use_excl; dotlock_set.nfs_flush = mail_set->mail_nfs_storage; dotlock_set.temp_prefix = temp_prefix; dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT; dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT; mailbox_list_get_root_permissions(list, &perm); fd_out = file_dotlock_open_group(&dotlock_set, path, 0, perm.file_create_mode, perm.file_create_gid, perm.file_create_gid_origin, &dotlock); if (fd_out == -1 && errno == ENOENT) { /* directory hasn't been created yet. */ type = list->set.control_dir != NULL ? MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR; fname = strrchr(path, '/'); if (fname != NULL) { dir = t_strdup_until(path, fname); if (mailbox_list_mkdir_root(list, dir, type) < 0) return -1; } fd_out = file_dotlock_open_group(&dotlock_set, path, 0, perm.file_create_mode, perm.file_create_gid, perm.file_create_gid_origin, &dotlock); } if (fd_out == -1) { if (errno == EAGAIN) { mailbox_list_set_error(list, MAIL_ERROR_TEMP, "Timeout waiting for subscription file lock"); } else { subswrite_set_syscall_error(list, "file_dotlock_open()", path); } return -1; } fd_in = nfs_safe_open(path, O_RDONLY); if (fd_in == -1 && errno != ENOENT) { subswrite_set_syscall_error(list, "open()", path); file_dotlock_delete(&dotlock); return -1; } if (fd_in != -1) { input = i_stream_create_fd_autoclose(&fd_in, list->mailbox_name_max_length+1); i_stream_set_return_partial_line(input, TRUE); subsfile_list_read_header(list, input, &version); } found = FALSE; output = o_stream_create_fd_file(fd_out, 0, FALSE); o_stream_cork(output); if (version >= 2) o_stream_send_str(output, version2_header); if (version < 2 || name[0] == '\0') escaped_name = name; else { const char *const *tmp; char separators[2]; string_t *str = t_str_new(64); separators[0] = mailbox_list_get_hierarchy_sep(list); separators[1] = '\0'; tmp = t_strsplit(name, separators); str_append_tabescaped(str, *tmp); for (tmp++; *tmp != NULL; tmp++) { str_append_c(str, '\t'); str_append_tabescaped(str, *tmp); } escaped_name = str_c(str); } if (input != NULL) { while ((line = next_line(list, path, input, &failed, FALSE)) != NULL) { if (strcmp(line, escaped_name) == 0) { found = TRUE; if (!set) { changed = TRUE; continue; } } o_stream_nsend_str(output, line); o_stream_nsend(output, "\n", 1); } i_stream_destroy(&input); } if (!failed && set && !found) { /* append subscription */ line = t_strconcat(escaped_name, "\n", NULL); o_stream_nsend_str(output, line); changed = TRUE; } if (changed && !failed) { if (o_stream_nfinish(output) < 0) { subswrite_set_syscall_error(list, "write()", path); failed = TRUE; } else if (mail_set->parsed_fsync_mode != FSYNC_MODE_NEVER) { if (fsync(fd_out) < 0) { subswrite_set_syscall_error(list, "fsync()", path); failed = TRUE; } } } else { o_stream_ignore_last_errors(output); } o_stream_destroy(&output); if (failed || !changed) { if (file_dotlock_delete(&dotlock) < 0) { subswrite_set_syscall_error(list, "file_dotlock_delete()", path); failed = TRUE; } } else { enum dotlock_replace_flags flags = DOTLOCK_REPLACE_FLAG_VERIFY_OWNER; if (file_dotlock_replace(&dotlock, flags) < 0) { subswrite_set_syscall_error(list, "file_dotlock_replace()", path); failed = TRUE; } } return failed ? -1 : (changed ? 1 : 0); }
static int mail_transaction_log_file_create2(struct mail_transaction_log_file *file, int new_fd, bool reset, struct dotlock **dotlock) { struct mail_index *index = file->log->index; struct stat st; const char *path2; buffer_t *writebuf; int fd, ret; bool rename_existing, need_lock; need_lock = file->log->head != NULL && file->log->head->locked; if (fcntl(new_fd, F_SETFL, O_APPEND) < 0) { log_file_set_syscall_error(file, "fcntl(O_APPEND)"); return -1; } if (file->log->nfs_flush) { /* although we check also mtime and file size below, it's done only to fix broken log files. we don't bother flushing attribute cache just for that. */ nfs_flush_file_handle_cache(file->filepath); } /* log creation is locked now - see if someone already created it. note that if we're rotating, we need to keep the log locked until the file has been rewritten. and because fcntl() locks are stupid, if we go and open()+close() the file and we had it already opened, its locks are lost. so we use stat() to check if the file has been recreated, although it almost never is. */ if (reset) rename_existing = FALSE; else if (nfs_safe_stat(file->filepath, &st) < 0) { if (errno != ENOENT) { log_file_set_syscall_error(file, "stat()"); return -1; } rename_existing = FALSE; } else if (st.st_ino == file->st_ino && CMP_DEV_T(st.st_dev, file->st_dev) && /* inode/dev checks are enough when we're rotating the file, but not when we're replacing a broken log file */ st.st_mtime == file->last_mtime && (uoff_t)st.st_size == file->last_size) { /* no-one else recreated the file */ rename_existing = TRUE; } else { /* recreated. use the file if its header is ok */ fd = nfs_safe_open(file->filepath, O_RDWR | O_APPEND); if (fd == -1) { if (errno != ENOENT) { log_file_set_syscall_error(file, "open()"); return -1; } } else { file->fd = fd; file->last_size = 0; if (mail_transaction_log_file_read_hdr(file, FALSE) > 0 && mail_transaction_log_file_stat(file, FALSE) == 0) { /* yes, it was ok */ file_dotlock_delete(dotlock); mail_transaction_log_file_add_to_list(file); return 0; } file->fd = -1; if (close(fd) < 0) log_file_set_syscall_error(file, "close()"); } rename_existing = FALSE; } if (index->fd == -1 && !rename_existing) { /* creating the initial index */ reset = TRUE; } if (mail_transaction_log_init_hdr(file->log, &file->hdr) < 0) return -1; if (reset) { /* don't reset modseqs. if we're reseting due to rebuilding indexes we'll probably want to keep uidvalidity and in such cases we really don't want to shrink modseqs. */ file->hdr.prev_file_seq = 0; file->hdr.prev_file_offset = 0; } writebuf = buffer_create_dynamic(pool_datastack_create(), 128); buffer_append(writebuf, &file->hdr, sizeof(file->hdr)); if (index->ext_hdr_init_data != NULL && reset) log_write_ext_hdr_init_data(index, writebuf); if (write_full(new_fd, writebuf->data, writebuf->used) < 0) { log_file_set_syscall_error(file, "write_full()"); return -1; } if (file->log->index->fsync_mode == FSYNC_MODE_ALWAYS) { /* the header isn't important, so don't bother calling fdatasync() unless it's required */ if (fdatasync(new_fd) < 0) { log_file_set_syscall_error(file, "fdatasync()"); return -1; } } file->fd = new_fd; ret = mail_transaction_log_file_stat(file, FALSE); if (need_lock) { /* we'll need to preserve the lock */ if (mail_transaction_log_file_lock(file) < 0) ret = -1; } /* if we return -1 the dotlock deletion code closes the fd */ file->fd = -1; if (ret < 0) return -1; /* keep two log files */ if (rename_existing) { /* rename() would be nice and easy way to do this, except then there's a race condition between the rename and file_dotlock_replace(). during that time the log file doesn't exist, which could cause problems. */ path2 = t_strconcat(file->filepath, ".2", NULL); if (i_unlink_if_exists(path2) < 0) { /* try to link() anyway */ } if (nfs_safe_link(file->filepath, path2, FALSE) < 0 && errno != ENOENT && errno != EEXIST) { mail_index_set_error(index, "link(%s, %s) failed: %m", file->filepath, path2); /* ignore the error. we don't care that much about the second log file and we're going to overwrite this first one. */ } /* NOTE: here's a race condition where both .log and .log.2 point to the same file. our reading code should ignore that though by comparing the inodes. */ } if (file_dotlock_replace(dotlock, DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) <= 0) return -1; /* success */ file->fd = new_fd; mail_transaction_log_file_add_to_list(file); i_assert(!need_lock || file->locked); return 1; }
int subsfile_set_subscribed(struct mailbox_list *list, const char *path, const char *temp_prefix, const char *name, bool set) { const struct mail_storage_settings *mail_set = list->mail_set; struct dotlock_settings dotlock_set; struct dotlock *dotlock; const char *line, *origin; struct istream *input; struct ostream *output; int fd_in, fd_out; mode_t mode; gid_t gid; bool found, changed = FALSE, failed = FALSE; if (strcasecmp(name, "INBOX") == 0) name = "INBOX"; memset(&dotlock_set, 0, sizeof(dotlock_set)); dotlock_set.use_excl_lock = mail_set->dotlock_use_excl; dotlock_set.nfs_flush = mail_set->mail_nfs_storage; dotlock_set.temp_prefix = temp_prefix; dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT; dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT; mailbox_list_get_permissions(list, NULL, &mode, &gid, &origin); fd_out = file_dotlock_open_group(&dotlock_set, path, 0, mode, gid, origin, &dotlock); if (fd_out == -1 && errno == ENOENT) { /* directory hasn't been created yet. */ if (mailbox_list_create_parent_dir(list, NULL, path) < 0) return -1; fd_out = file_dotlock_open_group(&dotlock_set, path, 0, mode, gid, origin, &dotlock); } if (fd_out == -1) { if (errno == EAGAIN) { mailbox_list_set_error(list, MAIL_ERROR_TEMP, "Timeout waiting for subscription file lock"); } else { subswrite_set_syscall_error(list, "file_dotlock_open()", path); } return -1; } fd_in = nfs_safe_open(path, O_RDONLY); if (fd_in == -1 && errno != ENOENT) { subswrite_set_syscall_error(list, "open()", path); (void)file_dotlock_delete(&dotlock); return -1; } input = fd_in == -1 ? NULL : i_stream_create_fd(fd_in, list->mailbox_name_max_length+1, TRUE); output = o_stream_create_fd_file(fd_out, 0, FALSE); o_stream_cork(output); found = FALSE; while ((line = next_line(list, path, input, &failed, FALSE)) != NULL) { if (strcmp(line, name) == 0) { found = TRUE; if (!set) { changed = TRUE; continue; } } (void)o_stream_send_str(output, line); (void)o_stream_send(output, "\n", 1); } if (!failed && set && !found) { /* append subscription */ line = t_strconcat(name, "\n", NULL); (void)o_stream_send_str(output, line); changed = TRUE; } if (changed && !failed) { if (o_stream_flush(output) < 0) { subswrite_set_syscall_error(list, "write()", path); failed = TRUE; } else if (mail_set->parsed_fsync_mode != FSYNC_MODE_NEVER) { if (fsync(fd_out) < 0) { subswrite_set_syscall_error(list, "fsync()", path); failed = TRUE; } } } if (input != NULL) i_stream_destroy(&input); o_stream_destroy(&output); if (failed || !changed) { if (file_dotlock_delete(&dotlock) < 0) { subswrite_set_syscall_error(list, "file_dotlock_delete()", path); failed = TRUE; } } else { enum dotlock_replace_flags flags = DOTLOCK_REPLACE_FLAG_VERIFY_OWNER; if (file_dotlock_replace(&dotlock, flags) < 0) { subswrite_set_syscall_error(list, "file_dotlock_replace()", path); failed = TRUE; } } return failed ? -1 : (changed ? 1 : 0); }