Exemple #1
0
static int mbox_mailbox_open(struct mailbox *box)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;
	struct stat st;
	int ret;

	if (box->input != NULL) {
		i_stream_ref(box->input);
		mbox->mbox_file_stream = box->input;
		mbox->backend_readonly = TRUE;
		mbox->backend_readonly_set = TRUE;
		mbox->no_mbox_file = TRUE;
		return mbox_mailbox_open_finish(mbox, FALSE);
	}

	ret = stat(mailbox_get_path(box), &st);
	if (ret == 0) {
		if (!S_ISDIR(st.st_mode))
			return mbox_mailbox_open_existing(mbox);
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
				       "Mailbox isn't selectable");
		return -1;
	} else if (ENOTFOUND(errno)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
			T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
		return -1;
	} else if (mail_storage_set_error_from_errno(box->storage)) {
		return -1;
	} else {
		mail_storage_set_critical(box->storage,
			"stat(%s) failed: %m", mailbox_get_path(box));
		return -1;
	}
}
static struct maildir_sync_context *
maildir_sync_context_new(struct maildir_mailbox *mbox,
			 enum mailbox_sync_flags flags)
{
        struct maildir_sync_context *ctx;

	ctx = t_new(struct maildir_sync_context, 1);
	ctx->mbox = mbox;
	ctx->new_dir = t_strconcat(mailbox_get_path(&mbox->box), "/new", NULL);
	ctx->cur_dir = t_strconcat(mailbox_get_path(&mbox->box), "/cur", NULL);
	ctx->last_touch = ioloop_time;
	ctx->last_notify = ioloop_time;
	ctx->flags = flags;
	return ctx;
}
Exemple #3
0
static struct mail_save_context *
maildir_save_transaction_init(struct mailbox_transaction_context *t)
{
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)t->box;
	struct maildir_save_context *ctx;
	const char *path;
	pool_t pool;

	pool = pool_alloconly_create("maildir_save_context", 4096);
	ctx = p_new(pool, struct maildir_save_context, 1);
	ctx->ctx.transaction = t;
	ctx->pool = pool;
	ctx->mbox = mbox;
	ctx->trans = t->itrans;
	ctx->files_tail = &ctx->files;
	ctx->fd = -1;

	path = mailbox_get_path(&mbox->box);
	ctx->tmpdir = p_strconcat(pool, path, "/tmp", NULL);
	ctx->newdir = p_strconcat(pool, path, "/new", NULL);
	ctx->curdir = p_strconcat(pool, path, "/cur", NULL);

	buffer_create_from_const_data(&ctx->keywords_buffer, "", 0);
	array_create_from_buffer(&ctx->keywords_array, &ctx->keywords_buffer,
				 sizeof(unsigned int));
	ctx->last_save_finished = TRUE;
	return &ctx->ctx;
}
Exemple #4
0
static const char *cydir_mail_get_path(struct mail *mail)
{
	const char *dir;

	dir = mailbox_get_path(mail->box);
	return t_strdup_printf("%s/%u.", dir, mail->uid);
}
Exemple #5
0
static int zlib_mailbox_open_input(struct mailbox *box)
{
	const struct compression_handler *handler;
	struct istream *input;
	struct stat st;
	int fd;

	handler = compression_lookup_handler_from_ext(box->name);
	if (handler == NULL || handler->create_istream == NULL)
		return 0;

	if (mail_storage_is_mailbox_file(box->storage)) {
		/* looks like a compressed single file mailbox. we should be
		   able to handle this. */
		const char *box_path = mailbox_get_path(box);

		fd = open(box_path, O_RDONLY);
		if (fd == -1) {
			/* let the standard handler figure out what to do
			   with the failure */
			return 0;
		}
		if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) {
			i_close_fd(&fd);
			return 0;
		}
		input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
		i_stream_set_name(input, box_path);
		box->input = handler->create_istream(input, TRUE);
		i_stream_unref(&input);
		box->flags |= MAILBOX_FLAG_READONLY;
	}
	return 0;
}
static void
maildir_sync_index_update_ext_header(struct maildir_index_sync_context *ctx)
{
	struct maildir_mailbox *mbox = ctx->mbox;
	const char *cur_path;
	const void *data;
	size_t data_size;
	struct stat st;

	cur_path = t_strconcat(mailbox_get_path(&mbox->box), "/cur", NULL);
	if (ctx->update_maildir_hdr_cur && stat(cur_path, &st) == 0) {
		if ((time_t)mbox->maildir_hdr.cur_check_time < st.st_mtime)
			mbox->maildir_hdr.cur_check_time = st.st_mtime;
		mbox->maildir_hdr.cur_mtime = st.st_mtime;
		mbox->maildir_hdr.cur_mtime_nsecs = ST_MTIME_NSEC(st);
	}

	mail_index_get_header_ext(mbox->box.view, mbox->maildir_ext_id,
				  &data, &data_size);
	if (data_size != sizeof(mbox->maildir_hdr) ||
	    maildir_index_header_has_changed(data, &mbox->maildir_hdr)) {
		mail_index_update_header_ext(ctx->trans, mbox->maildir_ext_id,
					     0, &mbox->maildir_hdr,
					     sizeof(mbox->maildir_hdr));
	}
}
Exemple #7
0
static int create_inbox(struct mailbox *box)
{
	const char *inbox_path;
	int fd;

	inbox_path = mailbox_get_path(box);

	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) {
		i_close_fd(&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;
	}
}
Exemple #8
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()");
		}
	}
}
Exemple #9
0
static int mbox_mailbox_open_existing(struct mbox_mailbox *mbox)
{
	struct mailbox *box = &mbox->box;
	const char *rootdir, *box_path = mailbox_get_path(box);
	bool move_to_memory;

	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_root_forced(box->list,
						       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 mbox_mailbox_open_finish(mbox, move_to_memory);
}
Exemple #10
0
static int
mbox_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
		    bool directory)
{
	int fd, ret;

	if ((ret = index_storage_mailbox_create(box, directory)) <= 0)
		return ret;

	if (box->inbox_any) {
		if (create_inbox(box) < 0)
			return -1;
	} else {
		/* create the mbox file */
		ret = mailbox_create_fd(box, mailbox_get_path(box),
					O_RDWR | O_CREAT | O_EXCL, &fd);
		if (ret < 0)
			return -1;
		if (ret == 0) {
			mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
					       "Mailbox already exists");
			return -1;
		}
		i_close_fd(&fd);
	}
	return update == NULL ? 0 : mbox_mailbox_update(box, update);
}
Exemple #11
0
int mbox_file_open_stream(struct mbox_mailbox *mbox)
{
	if (mbox->mbox_stream != NULL)
		return 0;

	if (mbox->mbox_file_stream != NULL) {
		/* read-only mbox stream */
		i_assert(mbox->mbox_fd == -1 && mbox_is_backend_readonly(mbox));
	} else {
		if (mbox->mbox_fd == -1) {
			if (mbox_file_open(mbox) < 0)
				return -1;
		}

		if (mbox->mbox_writeonly) {
			mbox->mbox_file_stream =
				i_stream_create_from_data("", 0);
		} else {
			mbox->mbox_file_stream =
				i_stream_create_fd(mbox->mbox_fd,
						   MBOX_READ_BLOCK_SIZE);
			i_stream_set_init_buffer_size(mbox->mbox_file_stream,
						      MBOX_READ_BLOCK_SIZE);
		}
		i_stream_set_name(mbox->mbox_file_stream,
				  mailbox_get_path(&mbox->box));
	}

	mbox->mbox_stream = i_stream_create_raw_mbox(mbox->mbox_file_stream);
	if (mbox->mbox_lock_type != F_UNLCK)
		istream_raw_mbox_set_locked(mbox->mbox_stream);
	return 0;
}
Exemple #12
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;
}
Exemple #13
0
static const char *
cydir_get_save_path(struct cydir_save_context *ctx, unsigned int num)
{
	const char *dir;

	dir = mailbox_get_path(&ctx->mbox->box);
	return t_strdup_printf("%s/%s.%u", dir, ctx->tmp_basename, num);
}
Exemple #14
0
static string_t *cydir_get_path_prefix(struct cydir_mailbox *mbox)
{
	string_t *path = str_new(default_pool, 256);

	str_append(path, mailbox_get_path(&mbox->box));
	str_append_c(path, '/');
	return path;
}
Exemple #15
0
static int maildir_file_do_try(struct maildir_mailbox *mbox, uint32_t uid,
			       maildir_file_do_func *callback, void *context)
{
	const char *path, *fname;
	enum maildir_uidlist_rec_flag flags;
	bool have_flags;
	int ret;

	ret = maildir_sync_lookup(mbox, uid, &flags, &fname);
	if (ret <= 0)
		return ret == 0 ? -2 : -1;

	if ((flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
		/* let's see if we can guess the filename based on index */
		fname = maildir_filename_guess(mbox, uid, fname,
					       &flags, &have_flags);
	}
	/* make a copy, just in case callback refreshes uidlist and
	   the pointer becomes invalid. */
	fname = t_strdup(fname);

	ret = 0;
	if ((flags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) {
		/* probably in new/ dir */
		path = t_strconcat(mailbox_get_path(&mbox->box),
				   "/new/", fname, NULL);
		ret = callback(mbox, path, context);
	}
	if (ret == 0) {
		path = t_strconcat(mailbox_get_path(&mbox->box), "/cur/",
				   fname, NULL);
		ret = callback(mbox, path, context);
	}
	if (ret > 0 && (flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
		/* file was found. make sure we remember its latest name. */
		maildir_uidlist_update_fname(mbox->uidlist, fname);
	} else if (ret == 0 &&
		   (flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) == 0) {
		/* file wasn't found. mark this message nonsynced, so we can
		   retry the lookup by guessing the flags */
		maildir_uidlist_add_flags(mbox->uidlist, fname,
					  MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
	}
	return ret;
}
Exemple #16
0
static void mbox_notify_changes(struct mailbox *box)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;

	if (box->notify_callback == NULL)
		mailbox_watch_remove_all(box);
	else if (!mbox->no_mbox_file)
		mailbox_watch_add(box, mailbox_get_path(box));
}
Exemple #17
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;
}
Exemple #18
0
void mbox_set_syscall_error(struct mbox_mailbox *mbox, const char *function)
{
	i_assert(function != NULL);

	if (ENOQUOTA(errno)) {
		mail_storage_set_error(&mbox->storage->storage,
			MAIL_ERROR_NOQUOTA, MAIL_ERRSTR_NO_QUOTA);
	} else {
		const char *toobig_error = errno != EFBIG ? "" :
			" (process was started with ulimit -f limit)";
		mail_storage_set_critical(&mbox->storage->storage,
			"%s failed with mbox file %s: %m%s", function,
			mailbox_get_path(&mbox->box), toobig_error);
	}
}
static int maildir_handle_uid_insertion(struct maildir_index_sync_context *ctx,
					enum maildir_uidlist_rec_flag uflags,
					const char *filename, uint32_t uid)
{
	int ret;

	if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
		/* partial syncing */
		return 0;
	}

	/* most likely a race condition: we read the maildir, then someone else
	   expunged messages and committed changes to index. so, this message
	   shouldn't actually exist. */
	if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) == 0) {
		/* mark it racy and check in next sync */
		ctx->mbox->maildir_hdr.cur_check_time = 0;
		maildir_sync_set_racing(ctx->maildir_sync_ctx);
		maildir_uidlist_add_flags(ctx->mbox->uidlist, filename,
					  MAILDIR_UIDLIST_REC_FLAG_RACING);
		return 0;
	}

	if (ctx->uidlist_sync_ctx == NULL) {
		ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
						MAILDIR_UIDLIST_SYNC_PARTIAL |
						MAILDIR_UIDLIST_SYNC_KEEP_STATE,
						&ctx->uidlist_sync_ctx);
		if (ret <= 0)
			return -1;
	}

	uflags &= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR;
	maildir_uidlist_sync_remove(ctx->uidlist_sync_ctx, filename);
	ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
					filename, uflags);
	i_assert(ret > 0);

	/* give the new UID to it immediately */
	maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);

	i_warning("Maildir %s: Expunged message reappeared, giving a new UID "
		  "(old uid=%u, file=%s)%s", mailbox_get_path(&ctx->mbox->box),
		  uid, filename, strncmp(filename, "msg.", 4) != 0 ? "" :
		  " (Your MDA is saving MH files into Maildir?)");
	return 0;
}
Exemple #20
0
int mbox_file_seek(struct mbox_mailbox *mbox, struct mail_index_view *view,
		   uint32_t seq, bool *deleted_r)
{
	uoff_t offset;
	int ret;

	ret = mbox_file_lookup_offset(mbox, view, seq, &offset);
	if (ret <= 0) {
		*deleted_r = ret < 0;
		return ret;
	}
	*deleted_r = FALSE;

	if (istream_raw_mbox_seek(mbox->mbox_stream, offset) < 0) {
		if (offset == 0) {
			mbox->invalid_mbox_file = TRUE;
			mail_storage_set_error(&mbox->storage->storage,
				MAIL_ERROR_NOTPOSSIBLE,
				"Mailbox isn't a valid mbox file");
			return -1;
		}

		if (mbox->mbox_hdr.dirty_flag != 0)
			return 0;

		mail_storage_set_critical(&mbox->storage->storage,
			"Cached message offset %s is invalid for mbox file %s",
			dec2str(offset), mailbox_get_path(&mbox->box));
		mbox->mbox_hdr.dirty_flag = 1;
		mbox->mbox_broken_offsets = TRUE;
		return 0;
	}

	if (mbox->mbox_hdr.dirty_flag != 0) {
		/* we're dirty - make sure this is the correct mail */
		if (!mbox_sync_parse_match_mail(mbox, view, seq))
			return 0;

		ret = istream_raw_mbox_seek(mbox->mbox_stream, offset);
		i_assert(ret == 0);
	}

	return 1;
}
static int
raw_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
		     const char **value_r)
{
	struct raw_mailbox *mbox = (struct raw_mailbox *)_mail->box;

	switch (field) {
	case MAIL_FETCH_FROM_ENVELOPE:
		*value_r = mbox->envelope_sender != NULL ?
			mbox->envelope_sender : "";
		return 0;
	case MAIL_FETCH_UIDL_FILE_NAME:
		*value_r = mbox->have_filename ?
			mailbox_get_path(_mail->box) : "";
		return 0;
	default:
		return index_mail_get_special(_mail, field, value_r);
	}
}
Exemple #22
0
static int
maildir_rename_empty_basename(struct maildir_sync_context *ctx,
			      const char *dir, const char *fname)
{
	const char *old_path, *new_fname, *new_path;

	old_path = t_strconcat(dir, "/", fname, NULL);
	new_fname = maildir_filename_generate();
	new_path = t_strconcat(mailbox_get_path(&ctx->mbox->box),
			       "/new/", new_fname, NULL);
	if (rename(old_path, new_path) == 0)
		i_warning("Fixed broken filename: %s -> %s", old_path, new_fname);
	else if (errno != ENOENT) {
		mail_storage_set_critical(&ctx->mbox->storage->storage,
			"Couldn't fix a broken filename: rename(%s, %s) failed: %m",
			old_path, new_path);
		return -1;
	}
	return 0;
}
static int maildir_sync_index_finish(struct maildir_index_sync_context *ctx,
				     bool success)
{
	struct maildir_mailbox *mbox = ctx->mbox;
	unsigned int time_diff;
	int ret = success ? 0 : -1;

	time_diff = time(NULL) - ctx->start_time;
	if (time_diff >= MAILDIR_SYNC_TIME_WARN_SECS) {
		i_warning("Maildir %s: Synchronization took %u seconds "
			  "(%u new msgs, %u flag change attempts, "
			  "%u expunge attempts)",
			  mailbox_get_path(&ctx->mbox->box), time_diff,
			  ctx->new_msgs_count, ctx->flag_change_count,
			  ctx->expunge_count);
		mail_index_sync_no_warning(ctx->sync_ctx);
	}

	if (ret < 0)
		mail_index_sync_rollback(&ctx->sync_ctx);
	else {
		maildir_sync_index_update_ext_header(ctx);

		/* Set syncing_commit=TRUE so that if any sync callbacks try
		   to access mails which got lost (eg. expunge callback trying
		   to open the file which was just unlinked) we don't try to
		   start a second index sync and crash. */
		mbox->syncing_commit = TRUE;
		if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
			mailbox_set_index_error(&mbox->box);
			ret = -1;
		}
		mbox->syncing_commit = FALSE;
	}

	index_storage_expunging_deinit(&mbox->box);
	maildir_keywords_sync_deinit(&ctx->keywords_sync_ctx);
	index_sync_changes_deinit(&ctx->sync_changes);
	i_free(ctx);
	return ret;
}
Exemple #24
0
int sdbox_read_header(struct sdbox_mailbox *mbox,
		      struct sdbox_index_header *hdr, bool log_error,
		      bool *need_resize_r)
{
	struct mail_index_view *view;
	const void *data;
	size_t data_size;
	int ret = 0;

	i_assert(mbox->box.opened);

	view = mail_index_view_open(mbox->box.index);
	mail_index_get_header_ext(view, mbox->hdr_ext_id,
				  &data, &data_size);
	if (data_size < SDBOX_INDEX_HEADER_MIN_SIZE &&
	    (!mbox->box.creating || data_size != 0)) {
		if (log_error) {
			mail_storage_set_critical(
				&mbox->storage->storage.storage,
				"sdbox %s: Invalid dbox header size",
				mailbox_get_path(&mbox->box));
		}
		ret = -1;
	} else {
		memset(hdr, 0, sizeof(*hdr));
		memcpy(hdr, data, I_MIN(data_size, sizeof(*hdr)));
		if (guid_128_is_empty(hdr->mailbox_guid))
			ret = -1;
		else {
			/* data is valid. remember it in case mailbox
			   is being reset */
			mail_index_set_ext_init_data(mbox->box.index,
						     mbox->hdr_ext_id,
						     hdr, sizeof(*hdr));
		}
	}
	mail_index_view_close(&view);
	*need_resize_r = data_size < sizeof(*hdr);
	return ret;
}
Exemple #25
0
int mbox_file_lookup_offset(struct mbox_mailbox *mbox,
			    struct mail_index_view *view,
			    uint32_t seq, uoff_t *offset_r)
{
	const void *data;
	bool deleted;

	mail_index_lookup_ext(view, seq, mbox->mbox_ext_idx, &data, &deleted);
	if (deleted)
		return -1;

	if (data == NULL) {
		mail_storage_set_critical(&mbox->storage->storage,
			"Cached message offset lost for seq %u in mbox file %s",
			seq, mailbox_get_path(&mbox->box));
		mbox->mbox_hdr.dirty_flag = 1;
                mbox->mbox_broken_offsets = TRUE;
		return 0;
	}

	*offset_r = *((const uint64_t *)data);
	return 1;
}
Exemple #26
0
int mdbox_read_header(struct mdbox_mailbox *mbox,
                      struct mdbox_index_header *hdr, bool *need_resize_r)
{
    const void *data;
    size_t data_size;

    i_assert(mbox->box.opened);

    mail_index_get_header_ext(mbox->box.view, mbox->hdr_ext_id,
                              &data, &data_size);
    if (data_size < MDBOX_INDEX_HEADER_MIN_SIZE &&
            (!mbox->creating || data_size != 0)) {
        mail_storage_set_critical(&mbox->storage->storage.storage,
                                  "mdbox %s: Invalid dbox header size: %"PRIuSIZE_T,
                                  mailbox_get_path(&mbox->box), data_size);
        mdbox_storage_set_corrupted(mbox->storage);
        return -1;
    }
    memset(hdr, 0, sizeof(*hdr));
    memcpy(hdr, data, I_MIN(data_size, sizeof(*hdr)));
    *need_resize_r = data_size < sizeof(*hdr);
    return 0;
}
Exemple #27
0
static int
maildir_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
			 const char **value_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box;
	const char *path, *fname = NULL, *end, *guid, *uidl, *order;
	struct stat st;

	switch (field) {
	case MAIL_FETCH_GUID:
		/* use GUID from uidlist if it exists */
		i_assert(!_mail->saving);

		if (mail->data.guid != NULL) {
			*value_r = mail->data.guid;
			return 0;
		}

		/* first make sure that we have a refreshed uidlist */
		if (maildir_mail_get_fname(mbox, _mail, &fname) <= 0)
			return -1;

		guid = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
						  MAILDIR_UIDLIST_REC_EXT_GUID);
		if (guid != NULL) {
			if (*guid != '\0') {
				*value_r = mail->data.guid =
					p_strdup(mail->mail.data_pool, guid);
				return 0;
			}

			mail_storage_set_critical(_mail->box->storage,
				"Maildir %s: Corrupted dovecot-uidlist: "
				"UID %u had empty GUID, clearing it",
				mailbox_get_path(_mail->box), _mail->uid);
			maildir_uidlist_unset_ext(mbox->uidlist, _mail->uid,
				MAILDIR_UIDLIST_REC_EXT_GUID);
		}

		/* default to base filename: */
		if (maildir_mail_get_special(_mail, MAIL_FETCH_STORAGE_ID,
					     value_r) < 0)
			return -1;
		mail->data.guid = mail->data.filename;
		return 0;
	case MAIL_FETCH_STORAGE_ID:
		if (mail->data.filename != NULL) {
			*value_r = mail->data.filename;
			return 0;
		}
		if (fname != NULL) {
			/* we came here from MAIL_FETCH_GUID,
			   avoid a second lookup */
		} else if (!_mail->saving) {
			if (maildir_mail_get_fname(mbox, _mail, &fname) <= 0)
				return -1;
		} else {
			path = maildir_save_file_get_path(_mail->transaction,
							  _mail->seq);
			fname = strrchr(path, '/');
			fname = fname != NULL ? fname + 1 : path;
		}
		end = strchr(fname, MAILDIR_INFO_SEP);
		mail->data.filename = end == NULL ?
			p_strdup(mail->mail.data_pool, fname) :
			p_strdup_until(mail->mail.data_pool, fname, end);
		*value_r = mail->data.filename;
		return 0;
	case MAIL_FETCH_UIDL_BACKEND:
		uidl = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
					MAILDIR_UIDLIST_REC_EXT_POP3_UIDL);
		if (uidl == NULL) {
			/* use the default */
			*value_r = "";
		} else if (*uidl == '\0') {
			/* special optimization case: use the base file name */
			return maildir_mail_get_special(_mail,
					MAIL_FETCH_STORAGE_ID, value_r);
		} else {
			*value_r = p_strdup(mail->mail.data_pool, uidl);
		}
		return 0;
	case MAIL_FETCH_POP3_ORDER:
		order = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
					MAILDIR_UIDLIST_REC_EXT_POP3_ORDER);
		if (order == NULL) {
			*value_r = "";
		} else {
			*value_r = p_strdup(mail->mail.data_pool, order);
		}
		return 0;
	case MAIL_FETCH_REFCOUNT:
		if (maildir_mail_stat(_mail, &st) < 0)
			return -1;
		*value_r = p_strdup_printf(mail->mail.data_pool, "%lu",
					   (unsigned long)st.st_nlink);
		return 0;
	default:
		return index_mail_get_special(_mail, field, value_r);
	}
}
Exemple #28
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;
}
Exemple #29
0
int mdbox_sync_begin(struct mdbox_mailbox *mbox, enum mdbox_sync_flags flags,
		     struct mdbox_map_atomic_context *atomic,
		     struct mdbox_sync_context **ctx_r)
{
	struct mail_storage *storage = mbox->box.storage;
	struct mdbox_sync_context *ctx;
	enum mail_index_sync_flags sync_flags;
	int ret;
	bool rebuild, storage_rebuilt = FALSE;

	*ctx_r = NULL;

	/* avoid race conditions with mailbox creation, don't check for dbox
	   headers until syncing has locked the mailbox */
	rebuild = mbox->storage->corrupted ||
		(flags & MDBOX_SYNC_FLAG_FORCE_REBUILD) != 0;
	if (rebuild && (flags & MDBOX_SYNC_FLAG_NO_REBUILD) == 0) {
		if (mdbox_storage_rebuild_in_context(mbox->storage, atomic) < 0)
			return -1;
		index_mailbox_reset_uidvalidity(&mbox->box);
		storage_rebuilt = TRUE;
	}

	ctx = i_new(struct mdbox_sync_context, 1);
	ctx->mbox = mbox;
	ctx->flags = flags;
	ctx->atomic = atomic;

	sync_flags = index_storage_get_sync_flags(&mbox->box);
	if (!rebuild && (flags & MDBOX_SYNC_FLAG_FORCE) == 0)
		sync_flags |= MAIL_INDEX_SYNC_FLAG_REQUIRE_CHANGES;
	if ((flags & MDBOX_SYNC_FLAG_FSYNC) != 0)
		sync_flags |= MAIL_INDEX_SYNC_FLAG_FSYNC;
	/* don't write unnecessary dirty flag updates */
	sync_flags |= MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES;

	ret = mdbox_sync_try_begin(ctx, sync_flags);
	if (ret <= 0) {
		/* failed / nothing to do */
		i_free(ctx);
		return ret;
	}

	if ((ret = mdbox_sync_index(ctx)) <= 0) {
		mail_index_sync_rollback(&ctx->index_sync_ctx);
		i_free_and_null(ctx);

		if (ret < 0)
			return -1;

		/* corrupted */
		if (storage_rebuilt) {
			mail_storage_set_critical(storage,
				"mdbox %s: Storage keeps breaking",
				mailbox_get_path(&mbox->box));
			return -1;
		}

		/* we'll need to rebuild storage.
		   try again from the beginning. */
		mdbox_storage_set_corrupted(mbox->storage);
		if ((flags & MDBOX_SYNC_FLAG_NO_REBUILD) != 0) {
			mail_storage_set_critical(storage,
				"mdbox %s: Can't rebuild storage",
				mailbox_get_path(&mbox->box));
			return -1;
		}
		return mdbox_sync_begin(mbox, flags, atomic, ctx_r);
	}

	*ctx_r = ctx;
	return 0;
}
int maildir_sync_index(struct maildir_index_sync_context *ctx,
		       bool partial)
{
	struct maildir_mailbox *mbox = ctx->mbox;
	struct mail_index_view *view = ctx->view;
	struct mail_index_view *view2;
	struct maildir_uidlist_iter_ctx *iter;
	struct mail_index_transaction *trans = ctx->trans;
	const struct mail_index_header *hdr;
	struct mail_index_header empty_hdr;
	const struct mail_index_record *rec;
	uint32_t seq, seq2, uid, prev_uid;
        enum maildir_uidlist_rec_flag uflags;
	const char *filename;
	uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid;
	uint32_t first_uid;
	unsigned int changes = 0;
	int ret = 0;
	time_t time_before_sync;
	guid_128_t expunged_guid_128;
	enum mail_flags private_flags_mask;
	bool expunged, full_rescan = FALSE;

	i_assert(!mbox->syncing_commit);

	first_uid = 1;
	hdr = mail_index_get_header(view);
	uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
	if (uid_validity != hdr->uid_validity &&
	    uid_validity != 0 && hdr->uid_validity != 0) {
		/* uidvalidity changed and index isn't being synced for the
		   first time, reset the index so we can add all messages as
		   new */
		i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
			  mailbox_get_path(&ctx->mbox->box),
			  hdr->uid_validity, uid_validity);
		mail_index_reset(trans);
		mailbox_recent_flags_reset(&mbox->box);

		first_uid = hdr->messages_count + 1;
		memset(&empty_hdr, 0, sizeof(empty_hdr));
		empty_hdr.next_uid = 1;
		hdr = &empty_hdr;
	}
	hdr_next_uid = hdr->next_uid;

	ctx->mbox->box.tmp_sync_view = view;
	private_flags_mask = mailbox_get_private_flags_mask(&mbox->box);
	time_before_sync = time(NULL);
	mbox->syncing_commit = TRUE;
	seq = prev_uid = 0; first_recent_uid = I_MAX(hdr->first_recent_uid, 1);
	i_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
	i_array_init(&ctx->idx_keywords, MAILDIR_MAX_KEYWORDS);
	iter = maildir_uidlist_iter_init(mbox->uidlist);
	while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
		maildir_filename_flags_get(ctx->keywords_sync_ctx, filename,
					   &ctx->flags, &ctx->keywords);

		i_assert(uid > prev_uid);
		prev_uid = uid;

		/* the private flags are kept only in indexes. don't use them
		   at all even for newly seen mails */
		ctx->flags &= ~private_flags_mask;

	again:
		seq++;
		ctx->uid = uid;

		if (seq > hdr->messages_count) {
			if (uid < hdr_next_uid) {
				if (maildir_handle_uid_insertion(ctx, uflags,
								 filename,
								 uid) < 0)
					ret = -1;
				seq--;
				continue;
			}

			/* Trust uidlist recent flags only for newly added
			   messages. When saving/copying messages with flags
			   they're stored to cur/ and uidlist treats them
			   as non-recent. */
			if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) == 0) {
				if (uid >= first_recent_uid)
					first_recent_uid = uid + 1;
			}

			hdr_next_uid = uid + 1;
			mail_index_append(trans, uid, &seq);
			mail_index_update_flags(trans, seq, MODIFY_REPLACE,
						ctx->flags);
			if (array_count(&ctx->keywords) > 0) {
				struct mail_keywords *kw;

				kw = mail_index_keywords_create_from_indexes(
					mbox->box.index, &ctx->keywords);
				mail_index_update_keywords(trans, seq,
							   MODIFY_REPLACE, kw);
				mail_index_keywords_unref(&kw);
			}
			continue;
		}

		rec = mail_index_lookup(view, seq);
		if (uid > rec->uid) {
			/* already expunged (no point in showing guid in the
			   expunge record anymore) */
			mail_index_expunge(ctx->trans, seq);
			goto again;
		}

		if (uid < rec->uid) {
			if (maildir_handle_uid_insertion(ctx, uflags,
							 filename, uid) < 0)
				ret = -1;
			seq--;
			continue;
		}

		index_sync_changes_read(ctx->sync_changes, ctx->uid, &expunged,
					expunged_guid_128);
		if (expunged) {
			if (!maildir_expunge_is_valid_guid(ctx, ctx->uid,
							   filename,
							   expunged_guid_128))
				continue;
			if (maildir_file_do(mbox, ctx->uid,
					    maildir_expunge, ctx) >= 0) {
				/* successful expunge */
				mail_index_expunge(ctx->trans, seq);
			}
			if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
				maildir_sync_notify(ctx->maildir_sync_ctx);
			continue;
		}

		/* the private flags are stored only in indexes, keep them */
		ctx->flags |= rec->flags & private_flags_mask;

		if (index_sync_changes_have(ctx->sync_changes)) {
			/* apply flag changes to maildir */
			if (maildir_file_do(mbox, ctx->uid,
					    maildir_sync_flags, ctx) < 0)
				ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
			if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
				maildir_sync_notify(ctx->maildir_sync_ctx);
		}

		if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
			/* partial syncing */
			if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) {
				/* we last saw this mail in new/, but it's
				   not there anymore. possibly expunged,
				   make sure. */
				full_rescan = TRUE;
			}
			continue;
		}

		if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
			/* we haven't been able to update maildir with this
			   record's flag changes. don't sync them. */
			continue;
		}

		if (ctx->flags != (rec->flags & MAIL_FLAGS_NONRECENT)) {
			mail_index_update_flags(trans, seq, MODIFY_REPLACE,
						ctx->flags);
		}

		maildir_sync_mail_keywords(ctx, seq);
	}
	maildir_uidlist_iter_deinit(&iter);

	if (!partial) {
		/* expunge the rest */
		for (seq++; seq <= hdr->messages_count; seq++)
			mail_index_expunge(ctx->trans, seq);
	}

	/* add \Recent flags. use updated view so it contains newly
	   appended messages. */
	view2 = mail_index_transaction_open_updated_view(trans);
	if (mail_index_lookup_seq_range(view2, first_recent_uid, (uint32_t)-1,
					&seq, &seq2) && seq2 >= first_uid) {
		if (seq < first_uid) {
			/* UIDVALIDITY changed, skip over the old messages */
			seq = first_uid;
		}
		mailbox_recent_flags_set_seqs(&mbox->box, view2, seq, seq2);
	}
	mail_index_view_close(&view2);

	if (ctx->uidlist_sync_ctx != NULL) {
		if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx,
						TRUE) < 0)
			ret = -1;
	}

	if (mbox->box.v.sync_notify != NULL)
		mbox->box.v.sync_notify(&mbox->box, 0, 0);
	ctx->mbox->box.tmp_sync_view = NULL;

	/* check cur/ mtime later. if we came here from saving messages they
	   could still be moved to cur/ directory. */
	ctx->update_maildir_hdr_cur = TRUE;
	mbox->maildir_hdr.cur_check_time = time_before_sync;

	if (uid_validity == 0) {
		uid_validity = hdr->uid_validity != 0 ? hdr->uid_validity :
			maildir_get_uidvalidity_next(mbox->box.list);
		maildir_uidlist_set_uid_validity(mbox->uidlist, uid_validity);
	}
	maildir_uidlist_set_next_uid(mbox->uidlist, hdr_next_uid, FALSE);

	if (uid_validity != hdr->uid_validity) {
		mail_index_update_header(trans,
			offsetof(struct mail_index_header, uid_validity),
			&uid_validity, sizeof(uid_validity), TRUE);
	}