Exemplo n.º 1
0
static void mbox_file_fix_atime(struct mbox_mailbox *mbox)
{
	struct utimbuf buf;
	struct stat st;

	if (mbox->box.recent_flags_count > 0 &&
	    (mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) == 0 &&
	    mbox->mbox_fd != -1 && !mbox_is_backend_readonly(mbox)) {
		/* we've seen recent messages which we want to keep recent.
		   keep file's atime lower than mtime so \Marked status
		   gets shown while listing */
		if (fstat(mbox->mbox_fd, &st) < 0) {
			mbox_set_syscall_error(mbox, "fstat()");
			return;
		}
		if (st.st_atime >= st.st_mtime) {
			buf.modtime = st.st_mtime;
			buf.actime = buf.modtime - 1;
			/* EPERM can happen with shared mailboxes */
			if (utime(mailbox_get_path(&mbox->box), &buf) < 0 &&
			    errno != EPERM)
				mbox_set_syscall_error(mbox, "utime()");
		}
	}
}
Exemplo n.º 2
0
static int mbox_seek_to_end(struct mbox_save_context *ctx, uoff_t *offset)
{
	struct stat st;
	char ch;
	int fd;

	if (ctx->mbox->mbox_writeonly) {
		*offset = 0;
		return 0;
	}

	fd = ctx->mbox->mbox_fd;
	if (fstat(fd, &st) < 0)
                return mbox_set_syscall_error(ctx->mbox, "fstat()");

	ctx->orig_atime = st.st_atime;

	*offset = (uoff_t)st.st_size;
	if (st.st_size == 0)
		return 0;

	if (lseek(fd, st.st_size-1, SEEK_SET) < 0)
                return mbox_set_syscall_error(ctx->mbox, "lseek()");

	if (read(fd, &ch, 1) != 1)
		return mbox_set_syscall_error(ctx->mbox, "read()");

	if (ch != '\n') {
		if (write_full(fd, "\n", 1) < 0)
			return write_error(ctx);
		*offset += 1;
	}

	return 0;
}
Exemplo n.º 3
0
static int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
{
	struct mbox_mailbox *mbox = ctx->mbox;
	struct stat st;

	if (ctx->checked_file || lock_type == F_UNLCK)
		return 0;

	if (mbox->mbox_fd != -1) {
		/* we could flush NFS file handle cache here if we wanted to
		   be sure that the file is latest, but mbox files get rarely
		   deleted and the flushing might cause errors (e.g. EBUSY for
		   trying to flush a /var/mail mountpoint) */
		if (nfs_safe_stat(mailbox_get_path(&mbox->box), &st) < 0) {
			if (errno == ENOENT)
				mailbox_set_deleted(&mbox->box);
			else
				mbox_set_syscall_error(mbox, "stat()");
			return -1;
		}

		if (st.st_ino != mbox->mbox_ino ||
		    !CMP_DEV_T(st.st_dev, mbox->mbox_dev))
			mbox_file_close(mbox);
	}

	if (mbox->mbox_fd == -1) {
		if (mbox_file_open(mbox) < 0)
			return -1;
	}

	ctx->checked_file = TRUE;
	return 0;
}
Exemplo n.º 4
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);
}
Exemplo n.º 5
0
int mbox_move(struct mbox_sync_context *sync_ctx,
	      uoff_t dest, uoff_t source, uoff_t size)
{
	struct istream *input;
	struct ostream *output;
	off_t ret;

	i_assert(size < OFF_T_MAX);

	if (size == 0 || source == dest)
		return 0;

	i_stream_sync(sync_ctx->input);

	output = o_stream_create_fd_file(sync_ctx->write_fd, (uoff_t)-1, FALSE);
	i_stream_seek(sync_ctx->file_input, source);
	if (o_stream_seek(output, dest) < 0) {
		mbox_set_syscall_error(sync_ctx->mbox,
				       "o_stream_seek()");
		o_stream_unref(&output);
		return -1;
	}

	input = i_stream_create_limit(sync_ctx->file_input, size);
	ret = o_stream_send_istream(output, input);
	i_stream_unref(&input);

        if (ret == (off_t)size)
		ret = 0;
	else if (ret >= 0) {
		mbox_sync_set_critical(sync_ctx,
			"mbox_move(%"PRIuUOFF_T", %"PRIuUOFF_T", %"PRIuUOFF_T
			") moved only %"PRIuUOFF_T" bytes",
			dest, source, size, (uoff_t)ret);
		ret = -1;
	} else if (ret < 0) {
		errno = output->stream_errno;
		mbox_set_syscall_error(sync_ctx->mbox,
				       "o_stream_send_istream()");
	}

	mbox_sync_file_updated(sync_ctx, FALSE);
	o_stream_destroy(&output);
	return (int)ret;
}
Exemplo n.º 6
0
static int mbox_write_content_length(struct mbox_save_context *ctx)
{
	uoff_t end_offset;
	const char *str;
	size_t len;

	i_assert(ctx->eoh_offset != (uoff_t)-1);

	if (ctx->mbox->mbox_writeonly) {
		/* we can't seek, don't set Content-Length */
		return 0;
	}

	end_offset = ctx->output->offset;

	/* write Content-Length headers */
	str = t_strdup_printf("\nContent-Length: %s",
			      dec2str(end_offset - ctx->eoh_offset));
	len = strlen(str);

	/* flush manually here so that we don't confuse seek() errors with
	   buffer flushing errors */
	if (o_stream_flush(ctx->output) < 0) {
		write_error(ctx);
		return -1;
	}
	if (o_stream_seek(ctx->output, ctx->extra_hdr_offset +
			  ctx->space_end_idx - len) < 0) {
		mbox_set_syscall_error(ctx->mbox, "lseek()");
		return -1;
	}

	if (o_stream_send(ctx->output, str, len) < 0 ||
	    o_stream_flush(ctx->output) < 0) {
		write_error(ctx);
		return -1;
	}

	if (o_stream_seek(ctx->output, end_offset) < 0) {
		mbox_set_syscall_error(ctx->mbox, "lseek()");
		return -1;
	}
	return 0;
}
Exemplo n.º 7
0
void mbox_file_close(struct mbox_mailbox *mbox)
{
	mbox_file_close_stream(mbox);

	if (mbox->mbox_fd != -1) {
		if (close(mbox->mbox_fd) < 0)
			mbox_set_syscall_error(mbox, "close()");
		mbox->mbox_fd = -1;
	}
}
Exemplo n.º 8
0
static int mbox_fill_space(struct mbox_sync_context *sync_ctx,
			   uoff_t offset, uoff_t size)
{
	unsigned char space[1024];

	memset(space, ' ', sizeof(space));
	while (size > sizeof(space)) {
		if (pwrite_full(sync_ctx->write_fd, space,
				sizeof(space), offset) < 0) {
			mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
			return -1;
		}
		size -= sizeof(space);
	}

	if (pwrite_full(sync_ctx->write_fd, space, size, offset) < 0) {
		mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
		return -1;
	}
	mbox_sync_file_updated(sync_ctx, TRUE);
	return 0;
}
Exemplo n.º 9
0
int mbox_file_open(struct mbox_mailbox *mbox)
{
	struct stat st;
	int fd;

	i_assert(mbox->mbox_fd == -1);

	if (mbox->mbox_file_stream != NULL) {
		/* read-only mbox stream */
		i_assert(mbox_is_backend_readonly(mbox));
		return 0;
	}

	fd = open(mailbox_get_path(&mbox->box),
		  mbox_is_backend_readonly(mbox) ? O_RDONLY : O_RDWR);
	if (fd == -1 && errno == EACCES && !mbox->backend_readonly) {
		mbox->backend_readonly = TRUE;
		fd = open(mailbox_get_path(&mbox->box), O_RDONLY);
	}

	if (fd == -1) {
		mbox_set_syscall_error(mbox, "open()");
		return -1;
	}

	if (fstat(fd, &st) < 0) {
		mbox_set_syscall_error(mbox, "fstat()");
		i_close_fd(&fd);
		return -1;
	}

	mbox->mbox_writeonly = S_ISFIFO(st.st_mode);
	mbox->mbox_fd = fd;
	mbox->mbox_dev = st.st_dev;
	mbox->mbox_ino = st.st_ino;
	return 0;
}
Exemplo n.º 10
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;
}
Exemplo n.º 11
0
static int write_error(struct mbox_save_context *ctx)
{
	mbox_set_syscall_error(ctx->mbox, "write()");
	ctx->failed = TRUE;
	return -1;
}
Exemplo n.º 12
0
static int mbox_sync_read_and_move(struct mbox_sync_context *sync_ctx,
                                   struct mbox_sync_mail_context *mail_ctx,
				   struct mbox_sync_mail *mails,
				   uint32_t seq, uint32_t idx, uint32_t padding,
				   off_t move_diff, uoff_t expunged_space,
				   uoff_t end_offset, bool first_nonexpunged)
{
	struct mbox_sync_mail_context new_mail_ctx;
	uoff_t offset, dest_offset;
	size_t need_space;

	if (mail_ctx == NULL) {
		if (mbox_sync_seek(sync_ctx, mails[idx].from_offset) < 0)
			return -1;

		mbox_sync_read_next(sync_ctx, &new_mail_ctx, mails, seq, idx,
				    expunged_space);
		mail_ctx = &new_mail_ctx;
	} else {
		i_assert(seq == mail_ctx->seq);
		if (mail_ctx->mail.space < 0)
			mail_ctx->mail.space = 0;
		i_stream_seek(sync_ctx->input, mail_ctx->body_offset);
	}

	if (mail_ctx->mail.space <= 0) {
		need_space = str_len(mail_ctx->header) - mail_ctx->mail.space -
			(mail_ctx->body_offset - mail_ctx->hdr_offset);
		if (need_space != (uoff_t)-mails[idx].space) {
			/* this check works only if we're doing the first
			   write, or if the file size was changed externally */
			mbox_sync_file_update_ext_modified(sync_ctx);

			mbox_sync_set_critical(sync_ctx,
				"seq=%u uid=%u uid_broken=%d "
				"originally needed %"PRIuUOFF_T
				" bytes, now needs %"PRIuSIZE_T" bytes",
				seq, mails[idx].uid, mails[idx].uid_broken,
				(uoff_t)-mails[idx].space, need_space);
		}
	}

	if (first_nonexpunged && expunged_space > 0) {
		/* move From-line (after parsing headers so we don't
		   overwrite them) */
		if (mbox_move(sync_ctx, mails[idx].from_offset - expunged_space,
			      mails[idx].from_offset,
			      mails[idx].offset - mails[idx].from_offset) < 0)
			return -1;
	}

	if (mails[idx].space == 0) {
		/* don't touch spacing */
	} else if (padding < (uoff_t)mail_ctx->mail.space) {
		mbox_sync_headers_remove_space(mail_ctx, mail_ctx->mail.space -
					       padding);
	} else {
		mbox_sync_headers_add_space(mail_ctx, padding -
					    mail_ctx->mail.space);
	}

	/* move the body of this message and headers of next message forward,
	   then write the headers */
	offset = sync_ctx->input->v_offset;
	dest_offset = offset + move_diff;
	i_assert(offset <= end_offset);
	if (mbox_move(sync_ctx, dest_offset, offset, end_offset - offset) < 0)
		return -1;

	/* the header may actually be moved backwards if there was expunged
	   space which we wanted to remove */
	i_assert(dest_offset >= str_len(mail_ctx->header));
	dest_offset -= str_len(mail_ctx->header);
	i_assert(dest_offset >= mails[idx].from_offset - expunged_space);
	if (pwrite_full(sync_ctx->write_fd, str_data(mail_ctx->header),
			str_len(mail_ctx->header), dest_offset) < 0) {
		mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
		return -1;
	}
	mbox_sync_file_updated(sync_ctx, TRUE);

	if (sync_ctx->dest_first_mail) {
		mbox_sync_first_mail_written(mail_ctx, dest_offset);
		sync_ctx->dest_first_mail = FALSE;
	}

	mails[idx].offset = dest_offset +
		(mail_ctx->mail.offset - mail_ctx->hdr_offset);
	mails[idx].space = mail_ctx->mail.space;
	return 0;
}
Exemplo n.º 13
0
int mbox_sync_try_rewrite(struct mbox_sync_mail_context *ctx, off_t move_diff)
{
        struct mbox_sync_context *sync_ctx = ctx->sync_ctx;
	size_t old_hdr_size, new_hdr_size;

	i_assert(sync_ctx->mbox->mbox_lock_type == F_WRLCK);

	old_hdr_size = ctx->body_offset - ctx->hdr_offset;
	new_hdr_size = str_len(ctx->header);

	if (new_hdr_size <= old_hdr_size) {
		/* add space. note that we must call add_space() even if we're
		   not adding anything so mail.offset gets fixed. */
		mbox_sync_headers_add_space(ctx, old_hdr_size - new_hdr_size);
	} else if (new_hdr_size > old_hdr_size) {
		/* try removing the space where we can */
		mbox_sync_headers_remove_space(ctx,
					       new_hdr_size - old_hdr_size);
		new_hdr_size = str_len(ctx->header);

		if (new_hdr_size <= old_hdr_size) {
			/* good, we removed enough. */
			i_assert(new_hdr_size == old_hdr_size);
		} else if (move_diff < 0 &&
			   new_hdr_size - old_hdr_size <= (uoff_t)-move_diff) {
			/* moving backwards - we can use the extra space from
			   it, just update expunged_space accordingly */
			i_assert(ctx->mail.space == 0);
			i_assert(sync_ctx->expunged_space >=
				 (off_t)(new_hdr_size - old_hdr_size));
			sync_ctx->expunged_space -= new_hdr_size - old_hdr_size;
		} else {
			/* couldn't get enough space */
			i_assert(ctx->mail.space == 0);
			ctx->mail.space =
				-(ssize_t)(new_hdr_size - old_hdr_size);
			return 0;
		}
	}

	i_assert(ctx->mail.space >= 0);

	if (ctx->header_first_change == (size_t)-1 && move_diff == 0) {
		/* no changes actually. we get here if index sync record told
		   us to do something that was already there */
		return 1;
	}

	if (move_diff != 0) {
		/* forget about partial write optimizations */
		ctx->header_first_change = 0;
		ctx->header_last_change = 0;
	}

	if (ctx->header_last_change != (size_t)-1 &&
	    ctx->header_last_change != 0)
		str_truncate(ctx->header, ctx->header_last_change);

	if (pwrite_full(sync_ctx->write_fd,
			str_data(ctx->header) + ctx->header_first_change,
			str_len(ctx->header) - ctx->header_first_change,
			ctx->hdr_offset + ctx->header_first_change +
			move_diff) < 0) {
		mbox_set_syscall_error(sync_ctx->mbox, "pwrite_full()");
		return -1;
	}

	if (sync_ctx->dest_first_mail &&
	    (ctx->imapbase_updated || ctx->sync_ctx->base_uid_last != 0)) {
		/* the position might have moved as a result of moving
		   whitespace */
		mbox_sync_first_mail_written(ctx, ctx->hdr_offset + move_diff);
	}

	mbox_sync_file_updated(sync_ctx, FALSE);
	return 1;
}
Exemplo n.º 14
0
static void write_error(struct mbox_save_context *ctx)
{
	mbox_set_syscall_error(ctx->mbox, "write()");
	ctx->failed = TRUE;
}