static int
imap_hibernate_client_input_line(struct connection *conn, const char *line)
{
	struct imap_hibernate_client *client =
		(struct imap_hibernate_client *)conn;
	int fd = -1, ret;

	if (!conn->version_received) {
		if (connection_verify_version(conn, t_strsplit_tabescaped(line)) < 0)
			return -1;
		conn->version_received = TRUE;
		return 1;
	}
	if (client->finished) {
		i_error("Received unexpected line: %s", line);
		return -1;
	}

	if (client->imap_client == NULL) {
		char *const *args;
		pool_t pool;

		fd = i_stream_unix_get_read_fd(conn->input);
		if (fd == -1) {
			i_error("IMAP client fd not received");
			return -1;
		}

		pool = pool_alloconly_create("client cmd", 1024);
		args = p_strsplit_tabescaped(pool, line);
		ret = imap_hibernate_client_input_args(conn, (void *)args, fd, pool);
		if (ret >= 0 && client->debug)
			i_debug("Create client with input: %s", line);
		pool_unref(&pool);
	} else {
		fd = i_stream_unix_get_read_fd(conn->input);
		if (fd == -1) {
			i_error("IMAP notify fd not received (input: %s)", line);
			ret = -1;
		} else if (line[0] != '\0') {
			i_error("Expected empty notify fd line from client, but got: %s", line);
			o_stream_send_str(conn->output,
					  "Expected empty notify fd line");
			ret = -1;
		} else {
			imap_client_add_notify_fd(client->imap_client, fd);
			ret = 1;
		}
	}

	if (ret < 0) {
		if (client->imap_client != NULL)
			imap_client_destroy(&client->imap_client, NULL);
		if (fd != -1)
			i_close_fd(&fd);
		return -1;
	} else if (ret == 0) {
		/* still need to read another fd */
		i_stream_unix_set_read_fd(conn->input);
	} else {
		/* finished - wait for disconnection from imap before finishing.
		   this way the old imap process will have time to destroy
		   itself before we have a chance to create another one. */
		client->finished = TRUE;
	}
	o_stream_send_str(conn->output, "+\n");
	return 1;
}
Exemple #2
0
int main(int argc, char *argv[])
{
	pid_t pid = fork();
	const char *path, *cmd;
	int fd_in[2], fd_out[2], fd_log, status;

	if (argc < 2)
		i_fatal("Usage: gdbhelper <program> [<args>]");

	switch (pid) {
	case 1:
		i_fatal("fork() failed: %m");
	case 0:
		/* child */
		(void)execvp(argv[1], argv+1);
		i_fatal("execvp(%s) failed: %m", argv[1]);
	default:
		if (pipe(fd_in) < 0 || pipe(fd_out) < 0)
			i_fatal("pipe() failed: %m");
		cmd = "handle SIGPIPE nostop\n"
			"handle SIGALRM nostop\n"
			"handle SIG32 nostop\n"
			"cont\n"
			"bt full\n"
			"quit\n";
		if (write(fd_in[1], cmd, strlen(cmd)) < 0)
			i_fatal("write() failed: %m");

		if (dup2(fd_in[0], 0) < 0 ||
		    dup2(fd_out[1], 1) < 0 ||
		    dup2(fd_out[1], 2) < 0)
			i_fatal("dup2() failed: %m");

		cmd = t_strdup_printf("gdb %s %s", argv[1], dec2str(pid));
		if (system(cmd) < 0)
			i_fatal("system() failed: %m");

		if (wait(&status) < 0)
			i_fatal("wait() failed: %m");
		if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
			char buf[1024];
			ssize_t ret;

			path = t_strdup_printf("/tmp/gdbhelper.%s.%s",
					       dec2str(time(NULL)),
					       dec2str(pid));
			fd_log = open(path, O_CREAT | O_WRONLY, 0600);
			if (fd_log < 0)
				i_fatal("open(%s) failed: %m", path);

			while ((ret = read(fd_out[0], buf, sizeof(buf))) > 0) {
				if (write(fd_log, buf, ret) < 0)
					i_fatal("write(%s) failed: %m", path);
			}
			if (ret < 0)
				i_fatal("read(pipe) failed: %m");
			i_close_fd(&fd_log);
		}
	}
	return 0;
}
Exemple #3
0
static void
ssl_params_if_unchanged(const char *path, time_t mtime,
			unsigned int ssl_dh_parameters_length ATTR_UNUSED)
{
	const char *temp_path;
	struct file_lock *lock;
	struct stat st, st2;
	mode_t old_mask;
	int fd, ret;

#ifdef HAVE_SETPRIORITY
	if (setpriority(PRIO_PROCESS, 0, SSL_PARAMS_PRIORITY) < 0)
		i_error("setpriority(%d) failed: %m", SSL_PARAMS_PRIORITY);
#endif

	temp_path = t_strconcat(path, ".tmp", NULL);

	old_mask = umask(0);
	fd = open(temp_path, O_WRONLY | O_CREAT, 0644);
	umask(old_mask);

	if (fd == -1)
		i_fatal("creat(%s) failed: %m", temp_path);

	/* If multiple dovecot instances are running, only one of them needs
	   to regenerate this file. */
	ret = file_wait_lock(fd, temp_path, F_WRLCK,
			     FILE_LOCK_METHOD_FCNTL,
			     SSL_BUILD_PARAM_TIMEOUT_SECS, &lock);
	if (ret < 0)
		i_fatal("file_try_lock(%s) failed: %m", temp_path);
	if (ret == 0) {
		/* someone else is writing this */
		i_fatal("Timeout while waiting for %s generation to complete",
			path);
	}

	/* make sure the .tmp file is still the one we created */
	if (fstat(fd, &st) < 0)
		i_fatal("fstat(%s) failed: %m", temp_path);
	if (stat(temp_path, &st2) < 0) {
		if (errno != ENOENT)
			i_fatal("stat(%s) failed: %m", temp_path);
		st2.st_ino = st.st_ino+1;
	}
	if (st.st_ino != st2.st_ino) {
		/* nope. so someone else just generated the file. */
		i_close_fd(&fd);
		return;
	}

	/* check that the parameters file is still the same */
	if (stat(path, &st) == 0) {
		if (st.st_mtime != mtime) {
			i_close_fd(&fd);
			return;
		}
	} else if (errno != ENOENT)
		i_fatal("stat(%s) failed: %m", path);

	/* ok, we really want to generate it. */
	if (ftruncate(fd, 0) < 0)
		i_fatal("ftruncate(%s) failed: %m", temp_path);

	i_info("Generating SSL parameters");
#ifdef HAVE_SSL
	ssl_generate_parameters(fd, ssl_dh_parameters_length, temp_path);
#endif

	if (rename(temp_path, path) < 0)
		i_fatal("rename(%s, %s) failed: %m", temp_path, path);
	if (close(fd) < 0)
		i_fatal("close(%s) failed: %m", temp_path);
	file_lock_free(&lock);

	i_info("SSL parameters regeneration completed");
}
Exemple #4
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) {
        mail_storage_set_critical(&mbox->storage->storage,
                                  "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) {
            mail_storage_set_critical(&mbox->storage->storage,
                                      "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) {
            mail_storage_set_critical(&mbox->storage->storage,
                                      "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);
            mail_storage_set_critical(&mbox->storage->storage,
                                      "%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) {
        mail_storage_set_critical(&mbox->storage->storage,
                                  "fchdir() failed: %m");
    }
    i_close_fd(&orig_dir_fd);
    errno = orig_errno;
    return ret;
}
static int acl_backend_vfile_acllist_read(struct acl_backend_vfile *backend)
{
	struct acl_backend_vfile_acllist acllist;
	struct istream *input;
	struct stat st;
	const char *path, *line, *p;
	int fd, ret = 0;

	backend->acllist_last_check = ioloop_time;

	if (!acl_list_get_path(backend, &path)) {
		/* we're never going to build acllist for this namespace. */
		acllist_clear(backend, 0);
		return 0;
	}

	if (backend->acllist_mtime != 0) {
		/* see if the file's mtime has changed */
		if (stat(path, &st) < 0) {
			if (errno == ENOENT)
				backend->acllist_mtime = 0;
			else
				i_error("stat(%s) failed: %m", path);
			return -1;
		}
		if (st.st_mtime == backend->acllist_mtime)
			return 0;
	}

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT) {
			backend->acllist_mtime = 0;
			return -1;
		}
		i_error("open(%s) failed: %m", path);
		return -1;
	}
	if (fstat(fd, &st) < 0) {
		i_error("fstat(%s) failed: %m", path);
		i_close_fd(&fd);
		return -1;
	}
	backend->acllist_mtime = st.st_mtime;
	acllist_clear(backend, st.st_size);

	input = i_stream_create_fd(fd, (size_t)-1, FALSE);
	while ((line = i_stream_read_next_line(input)) != NULL) {
		acllist.mtime = 0;
		for (p = line; *p >= '0' && *p <= '9'; p++)
			acllist.mtime = acllist.mtime * 10 + (*p - '0');

		if (p == line || *p != ' ' || p[1] == '\0') {
			i_error("Broken acllist file: %s", path);
			i_unlink_if_exists(path);
			i_close_fd(&fd);
			return -1;
		}
		acllist.name = p_strdup(backend->acllist_pool, p + 1);
		array_append(&backend->acllist, &acllist, 1);
	}
	if (input->stream_errno != 0)
		ret = -1;
	i_stream_destroy(&input);

	if (close(fd) < 0)
		i_error("close(%s) failed: %m", path);
	return ret;
}
Exemple #6
0
static int passwd_file_open(struct passwd_file *pw, bool startup,
			    const char **error_r)
{
	const char *no_args = NULL;
	struct istream *input;
	const char *line;
	struct stat st;
	time_t start_time, end_time;
	unsigned int time_secs;
	int fd;

	fd = open(pw->path, O_RDONLY);
	if (fd == -1) {
		if (errno == EACCES)
			*error_r = eacces_error_get("open", pw->path);
		else {
			*error_r = t_strdup_printf("open(%s) failed: %m",
						   pw->path);
		}
		return -1;
	}

	if (fstat(fd, &st) != 0) {
		*error_r = t_strdup_printf("fstat(%s) failed: %m",
					   pw->path);
		i_close_fd(&fd);
		return -1;
	}

	pw->fd = fd;
	pw->stamp = st.st_mtime;
	pw->size = st.st_size;

	pw->pool = pool_alloconly_create(MEMPOOL_GROWING"passwd_file", 10240);
	hash_table_create(&pw->users, pw->pool, 0, str_hash, strcmp);

	start_time = time(NULL);
	input = i_stream_create_fd(pw->fd, (size_t)-1, FALSE);
	i_stream_set_return_partial_line(input, TRUE);
	while ((line = i_stream_read_next_line(input)) != NULL) {
		if (*line == '\0' || *line == ':' || *line == '#')
			continue; /* no username or comment */

		T_BEGIN {
			const char *const *args = t_strsplit(line, ":");
			if (args[1] != NULL) {
				/* at least username+password */
				passwd_file_add(pw, args[0], args[1], args+2);
			} else {
				/* only username */
				passwd_file_add(pw, args[0], NULL, &no_args);
			}
		} T_END;
	}
	i_stream_destroy(&input);
	end_time = time(NULL);
	time_secs = end_time - start_time;

	if ((time_secs > PARSE_TIME_STARTUP_WARN_SECS && startup) ||
	    (time_secs > PARSE_TIME_RELOAD_WARN_SECS && !startup)) {
		i_warning("passwd-file %s: Reading %u users took %u secs",
			  pw->path, hash_table_count(pw->users), time_secs);
	} else if (pw->db->debug) {
		i_debug("passwd-file %s: Read %u users in %u secs",
			pw->path, hash_table_count(pw->users), time_secs);
	}
	return 0;
}
static int
cmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user)
{
	struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx;
	struct dsync_ibc *ibc, *ibc2 = NULL;
	struct dsync_brain *brain;
	struct dsync_brain_settings set;
	enum dsync_brain_flags brain_flags;
	bool remote_errors_logged = FALSE;
	bool changes_during_sync = FALSE;
	int status = 0, ret = 0;

	memset(&set, 0, sizeof(set));
	set.sync_box = ctx->mailbox;
	memcpy(set.sync_box_guid, ctx->mailbox_guid, sizeof(set.sync_box_guid));
	set.lock_timeout_secs = ctx->lock_timeout;
	set.state = ctx->state_input;
	if (array_count(&ctx->exclude_mailboxes) > 0) {
		/* array is NULL-terminated in init() */
		set.exclude_mailboxes = array_idx(&ctx->exclude_mailboxes, 0);
	}
	doveadm_user_init_dsync(user);

	if (ctx->namespace_prefix != NULL) {
		set.sync_ns = mail_namespace_find(user->namespaces,
						  ctx->namespace_prefix);
	}

	if (ctx->run_type == DSYNC_RUN_TYPE_LOCAL)
		dsync_ibc_init_pipe(&ibc, &ibc2);
	else {
		string_t *temp_prefix = t_str_new(64);
		mail_user_set_get_temp_prefix(temp_prefix, user->set);
		ibc = cmd_dsync_icb_stream_init(ctx, ctx->remote_name,
						str_c(temp_prefix));
		if (ctx->fd_err != -1) {
			ctx->io_err = io_add(ctx->fd_err, IO_READ,
					     remote_error_input, ctx);
		}
	}

	brain_flags = DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS;
	if (ctx->sync_visible_namespaces)
		brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES;

	if (ctx->reverse_backup)
		brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_RECV;
	else if (ctx->backup)
		brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_SEND;

	if (ctx->no_mail_sync)
		brain_flags |= DSYNC_BRAIN_FLAG_NO_MAIL_SYNC;
	if (ctx->oneway)
		brain_flags |= DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE;
	if (doveadm_debug)
		brain_flags |= DSYNC_BRAIN_FLAG_DEBUG;

	brain = dsync_brain_master_init(user, ibc, ctx->sync_type,
					brain_flags, &set);

	if (ctx->run_type == DSYNC_RUN_TYPE_LOCAL) {
		if (cmd_dsync_run_local(ctx, user, brain, ibc2,
					&changes_during_sync) < 0)
			ret = -1;
	} else {
		cmd_dsync_run_remote(user);
	}

	if (ctx->state_input != NULL) {
		string_t *state_str = t_str_new(128);
		dsync_brain_get_state(brain, state_str);
		doveadm_print(str_c(state_str));
	}

	if (dsync_brain_has_unexpected_changes(brain) || changes_during_sync) {
		i_warning("Mailbox changes caused a desync. "
			  "You may want to run dsync again.");
		ctx->ctx.exit_code = 2;
	}
	if (dsync_brain_deinit(&brain) < 0) {
		ctx->ctx.exit_code = EX_TEMPFAIL;
		ret = -1;
	}
	dsync_ibc_deinit(&ibc);
	if (ibc2 != NULL)
		dsync_ibc_deinit(&ibc2);
	if (ctx->ssl_iostream != NULL)
		ssl_iostream_destroy(&ctx->ssl_iostream);
	if (ctx->ssl_ctx != NULL)
		ssl_iostream_context_deinit(&ctx->ssl_ctx);
	if (ctx->fd_in != -1) {
		if (ctx->fd_out != ctx->fd_in)
			i_close_fd(&ctx->fd_out);
		i_close_fd(&ctx->fd_in);
	}
	if (ctx->run_type == DSYNC_RUN_TYPE_CMD)
		cmd_dsync_wait_remote(ctx, &status);

	/* print any final errors after the process has died. not closing
	   stdin/stdout before wait() may cause the process to hang, but stderr
	   shouldn't (at least with ssh) and we need stderr to be open to be
	   able to print the final errors */
	if (ctx->err_stream != NULL) {
		remote_error_input(ctx);
		remote_errors_logged = ctx->err_stream->v_offset > 0;
		i_stream_destroy(&ctx->err_stream);
	}
	if (ctx->run_type == DSYNC_RUN_TYPE_CMD)
		cmd_dsync_log_remote_status(status, remote_errors_logged);
	if (ctx->io_err != NULL)
		io_remove(&ctx->io_err);
	if (ctx->fd_err != -1)
		i_close_fd(&ctx->fd_err);
	ctx->input = NULL;
	ctx->output = NULL;

	return ret;
}
static void test_ostream_file_send_istream_file(void)
{
	struct istream *input, *input2;
	struct ostream *output;
	char buf[10];
	int fd;

	test_begin("ostream file send istream file");

	/* temp file istream */
	fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd == -1)
		i_fatal("creat(.temp.istream) failed: %m");
	test_assert(write(fd, "1234567890", 10) == 10);
	test_assert(lseek(fd, 0, SEEK_SET) == 0);
	input = i_stream_create_fd_autoclose(&fd, 1024);

	/* temp file ostream */
	fd = open(".temp.ostream", O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd == -1)
		i_fatal("creat(.temp.ostream) failed: %m");
	output = o_stream_create_fd(fd, 0);

	/* test that writing works between two files */
	i_stream_seek(input, 3);
	input2 = i_stream_create_limit(input, 4);
	test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED);
	test_assert(output->offset == 4);
	test_assert(pread(fd, buf, sizeof(buf), 0) == 4 &&
		    memcmp(buf, "4567", 4) == 0);
	i_stream_unref(&input2);

	/* test that writing works within the same file */
	i_stream_destroy(&input);

	input = i_stream_create_fd(fd, 1024);
	/* forwards: 4567 -> 4677 */
	o_stream_seek(output, 1);
	i_stream_seek(input, 2);
	input2 = i_stream_create_limit(input, 2);
	test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED);
	test_assert(output->offset == 3);
	test_assert(pread(fd, buf, sizeof(buf), 0) == 4 &&
		    memcmp(buf, "4677", 4) == 0);
	i_stream_destroy(&input2);
	i_stream_destroy(&input);

	/* backwards: 1234 -> 11234 */
	memcpy(buf, "1234", 4);
	test_assert(pwrite(fd, buf, 4, 0) == 4);
	input = i_stream_create_fd(fd, 1024);
	o_stream_seek(output, 1);
	test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED);
	test_assert(output->offset == 5);
	test_assert(pread(fd, buf, sizeof(buf), 0) == 5 &&
		    memcmp(buf, "11234", 5) == 0);
	i_stream_destroy(&input);

	o_stream_destroy(&output);
	i_close_fd(&fd);

	i_unlink(".temp.istream");
	i_unlink(".temp.ostream");
	test_end();
}
Exemple #9
0
static int
file_contents_equal(const char *path1, const char *path2, ino_t *path2_inode_r)
{
	struct stat st1, st2;
	int fd1, fd2, ret = -1;

	*path2_inode_r = 0;

	/* do a byte-by-byte comparison for the files to find out if they're
	   the same or if this is a hash collision */
	fd1 = open(path1, O_RDONLY);
	if (fd1 == -1) {
		if (errno != ENOENT)
			i_error("open(%s) failed: %m", path1);
		return -1;
	}
	fd2 = open(path2, O_RDONLY);
	if (fd1 == -1) {
		if (errno != ENOENT)
			i_error("open(%s) failed: %m", path2);
		i_close_fd(&fd1);
		return -1;
	}

	if (fstat(fd1, &st1) < 0)
		i_error("fstat(%s) failed: %m", path1);
	else if (fstat(fd2, &st2) < 0)
		i_error("fstat(%s) failed: %m", path1);
	else if (st1.st_size != st2.st_size)
		ret = 0;
	else {
		/* @UNSAFE: sizes match. compare. */
		unsigned char buf1[IO_BLOCK_SIZE], buf2[IO_BLOCK_SIZE];
		ssize_t ret1;
		int ret2;

		while ((ret1 = read(fd1, buf1, sizeof(buf1))) > 0) {
			if ((ret2 = read_full(fd2, buf2, ret1)) <= 0) {
				if (ret2 < 0)
					i_error("read(%s) failed: %m", path2);
				else
					ret = 0;
				break;
			}
			if (memcmp(buf1, buf2, ret1) != 0) {
				ret = 0;
				break;
			}
		}
		if (ret1 < 0)
			i_error("read(%s) failed: %m", path1);
		else if (ret1 == 0)
			ret = 1;
		*path2_inode_r = st2.st_ino;
	}

	if (close(fd1) < 0)
		i_error("close(%s) failed: %m", path1);
	if (close(fd2) < 0)
		i_error("close(%s) failed: %m", path2);

	return ret;
}
static uint32_t
mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path)
{
	DIR *d;
	struct dirent *dp;
	const char *fname, *dir, *prefix, *tmp;
	char *endp;
	unsigned int i, prefix_len;
	uint32_t cur_value, min_value, max_value;
	mode_t old_mask;
	int fd;

	fname = strrchr(path, '/');
	if (fname == NULL) {
		dir = ".";
		fname = path;
	} else {
		dir = t_strdup_until(path, fname);
		fname++;
	}

	d = opendir(dir);
	if (d == NULL && errno == ENOENT) {
		/* FIXME: the PATH_TYPE_CONTROL should come as a parameter, but
		   that's an API change, do it in v2.3. it's not really a
		   problem though, since currently all backends use control
		   dirs for the uidvalidity file. */
		(void)mailbox_list_mkdir_root(list, dir, MAILBOX_LIST_PATH_TYPE_CONTROL);
		d = opendir(dir);
	}
	if (d == NULL) {
		i_error("opendir(%s) failed: %m", dir);
		return mailbox_uidvalidity_next_fallback();
	}
	prefix = t_strconcat(fname, ".", NULL);
	prefix_len = strlen(prefix);

	/* just in case there happens to be multiple matching uidvalidity
	   files, track the min/max values. use the max value and delete the
	   min value file. */
	max_value = 0; min_value = (uint32_t)-1;
	while ((dp = readdir(d)) != NULL) {
		if (strncmp(dp->d_name, prefix, prefix_len) == 0) {
			cur_value = strtoul(dp->d_name + prefix_len, &endp, 16);
			if (*endp == '\0') {
				if (min_value > cur_value)
					min_value = cur_value;
				if (max_value < cur_value)
					max_value = cur_value;
			}
		}
	}
	if (closedir(d) < 0)
		i_error("closedir(%s) failed: %m", dir);

	if (max_value == 0) {
		/* no uidvalidity files. create one. */
		for (i = 0; i < RETRY_COUNT; i++) {
			cur_value = mailbox_uidvalidity_next_fallback();
			tmp = t_strdup_printf("%s.%08x", path, cur_value);
			/* the file is empty, don't bother with permissions */
			old_mask = umask(0);
			fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0444);
			umask(old_mask);
			if (fd != -1 || errno != EEXIST)
				break;
			/* already exists. although it's quite unlikely we'll
			   hit this race condition. more likely we'll create
			   a duplicate file.. */
		}
		if (fd == -1) {
			i_error("creat(%s) failed: %m", tmp);
			return cur_value;
		}
		i_close_fd(&fd);
		mailbox_uidvalidity_write(list, path, cur_value);
		return cur_value;
	}
	if (min_value != max_value) {
		/* duplicate uidvalidity files, delete the oldest */
		tmp = t_strdup_printf("%s.%08x", path, min_value);
		if (unlink(tmp) < 0 && errno != ENOENT)
			i_error("unlink(%s) failed: %m", tmp);
	}

	cur_value = max_value;
	if (mailbox_uidvalidity_rename(path, &cur_value, TRUE) < 0)
		return mailbox_uidvalidity_next_fallback();
	mailbox_uidvalidity_write(list, path, cur_value);
	return cur_value;
}
static int
acl_backend_vfile_read(struct acl_object *aclobj, bool global, const char *path,
		       struct acl_vfile_validity *validity, bool try_retry,
		       bool *is_dir_r)
{
	struct istream *input;
	struct stat st;
	struct acl_rights rights;
	const char *line, *error;
	unsigned int linenum;
	int fd, ret = 0;

	*is_dir_r = FALSE;

	fd = nfs_safe_open(path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT || errno == ENOTDIR) {
			if (aclobj->backend->debug)
				i_debug("acl vfile: file %s not found", path);
			validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND;
		} else if (errno == EACCES) {
			if (aclobj->backend->debug)
				i_debug("acl vfile: no access to file %s",
					path);

			acl_object_remove_all_access(aclobj);
			validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS;
		} else {
			i_error("open(%s) failed: %m", path);
			return -1;
		}

		validity->last_size = 0;
		validity->last_read_time = ioloop_time;
		return 1;
	}

	if (fstat(fd, &st) < 0) {
		if (errno == ESTALE && try_retry) {
			i_close_fd(&fd);
			return 0;
		}

		i_error("fstat(%s) failed: %m", path);
		i_close_fd(&fd);
		return -1;
	}
	if (S_ISDIR(st.st_mode)) {
		/* we opened a directory. */
		*is_dir_r = TRUE;
		i_close_fd(&fd);
		return 0;
	}

	if (aclobj->backend->debug)
		i_debug("acl vfile: reading file %s", path);

	input = i_stream_create_fd(fd, (size_t)-1, FALSE);
	i_stream_set_return_partial_line(input, TRUE);
	linenum = 1;
	while ((line = i_stream_read_next_line(input)) != NULL) {
		T_BEGIN {
			ret = acl_rights_parse_line(line, aclobj->rights_pool,
						    &rights, &error);
			rights.global = global;
			if (ret < 0) {
				i_error("ACL file %s line %u: %s",
					path, linenum, error);
			} else {
				array_append(&aclobj->rights, &rights, 1);
			}
		} T_END;
		if (ret < 0)
			break;
		linenum++;
	}

	if (ret < 0) {
		/* parsing failure */
	} else if (input->stream_errno != 0) {
		if (input->stream_errno == ESTALE && try_retry)
			ret = 0;
		else {
			ret = -1;
			i_error("read(%s) failed: %m", path);
		}
	} else {
		if (fstat(fd, &st) < 0) {
			if (errno == ESTALE && try_retry)
				ret = 0;
			else {
				ret = -1;
				i_error("fstat(%s) failed: %m", path);
			}
		} else {
			ret = 1;
			validity->last_read_time = ioloop_time;
			validity->last_mtime = st.st_mtime;
			validity->last_size = st.st_size;
		}
	}

	i_stream_unref(&input);
	if (close(fd) < 0) {
		if (errno == ESTALE && try_retry)
			return 0;

		i_error("close(%s) failed: %m", path);
		return -1;
	}
	return ret;
}