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;
}
Exemplo n.º 2
0
static int
unlink_directory_r(const char *dir, enum unlink_directory_flags flags)
{
	DIR *dirp;
	struct dirent *d;
	struct stat st;
        int dir_fd, old_errno;

#ifdef O_NOFOLLOW
	dir_fd = open(dir, O_RDONLY | O_NOFOLLOW);
	if (dir_fd == -1)
		return -1;
#else
	struct stat st2;

	if (lstat(dir, &st) < 0)
		return -1;

	if (!S_ISDIR(st.st_mode)) {
		if ((st.st_mode & S_IFMT) != S_IFLNK)
			errno = ENOTDIR;
		else {
			/* be compatible with O_NOFOLLOW */
			errno = ELOOP;
		}
		return -1;
	}

	dir_fd = open(dir, O_RDONLY);
	if (dir_fd == -1)
		return -1;

	if (fstat(dir_fd, &st2) < 0) {
		i_close_fd(&dir_fd);
		return -1;
	}

	if (st.st_ino != st2.st_ino ||
	    !CMP_DEV_T(st.st_dev, st2.st_dev)) {
		/* directory was just replaced with something else. */
		i_close_fd(&dir_fd);
		errno = ENOTDIR;
		return -1;
	}
#endif
	if (fchdir(dir_fd) < 0) {
                i_close_fd(&dir_fd);
		return -1;
	}

	dirp = opendir(".");
	if (dirp == NULL) {
		i_close_fd(&dir_fd);
		return -1;
	}

	errno = 0;
	while ((d = readdir(dirp)) != NULL) {
		if (d->d_name[0] == '.') {
			if ((d->d_name[1] == '\0' ||
			     (d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
				/* skip . and .. */
				continue;
			}
			if ((flags & UNLINK_DIRECTORY_FLAG_SKIP_DOTFILES) != 0)
				continue;
		}

		if (unlink(d->d_name) < 0 && errno != ENOENT) {
			old_errno = errno;

			if (lstat(d->d_name, &st) < 0) {
				if (errno != ENOENT)
					break;
				errno = 0;
			} else if (S_ISDIR(st.st_mode) &&
				   (flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) == 0) {
				if (unlink_directory_r(d->d_name, flags) < 0) {
					if (errno != ENOENT)
						break;
					errno = 0;
				}
				if (fchdir(dir_fd) < 0)
					break;

				if (rmdir(d->d_name) < 0) {
					if (errno != ENOENT) {
						if (errno == EEXIST) {
							/* standardize errno */
							errno = ENOTEMPTY;
						}
						break;
					}
					errno = 0;
				}
			} else if (S_ISDIR(st.st_mode) &&
				   (flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) != 0) {
				/* skip directory */
			} else if (old_errno == EBUSY &&
				   strncmp(d->d_name, ".nfs", 4) == 0) {
				/* can't delete NFS files that are still
				   in use. let the caller decide if this error
				   is worth logging about */
				break;
			} else {
                                /* so it wasn't a directory */
				errno = old_errno;
				i_error("unlink(%s/%s) failed: %m",
					dir, d->d_name);
				break;
			}
		}
	}
	old_errno = errno;

	i_close_fd(&dir_fd);
	if (closedir(dirp) < 0)
		return -1;

	if (old_errno != 0) {
		errno = old_errno;
		return -1;
	}

	return 0;
}
Exemplo n.º 3
0
static int
auth_token_read_secret(const char *path,
		       unsigned char secret_r[AUTH_TOKEN_SECRET_LEN])
{
	struct stat st, lst;
	int fd, ret;		

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		if (errno != ENOENT)
			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;
	}

	/* check secret len and file type */
	if (st.st_size != AUTH_TOKEN_SECRET_LEN || !S_ISREG(st.st_mode)) {
		i_error("Corrupted token secret file: %s", path);
		i_close_fd(&fd);
		i_unlink(path);
		return -1;
	}

	/* verify that we're not dealing with a symbolic link */
	if (lstat(path, &lst) < 0) {
		i_error("lstat(%s) failed: %m", path);
		i_close_fd(&fd);
		return -1;		
	}

	/* check security parameters for compromise */
	if ((st.st_mode & 07777) != 0600 ||
	    st.st_uid != geteuid() || st.st_nlink > 1 ||
	    !S_ISREG(lst.st_mode) || st.st_ino != lst.st_ino ||
	    !CMP_DEV_T(st.st_dev, lst.st_dev)) {
		i_error("Compromised token secret file: %s", path);
		i_close_fd(&fd);
		i_unlink(path);
		return -1;
	}

	/* FIXME: fail here to generate new secret if stored one is too old */

	ret = read_full(fd, secret_r, AUTH_TOKEN_SECRET_LEN);
	if (ret < 0)
		i_error("read(%s) failed: %m", path);
	else if (ret == 0) {
		i_error("Token secret file unexpectedly shrank: %s", path);
		ret = -1;
	}
	if (close(fd) < 0)
		i_error("close(%s) failed: %m", path);

	if (global_auth_settings->debug)
		i_debug("Read auth token secret from %s", path);
	return ret;
}
Exemplo n.º 4
0
static int maildir_fix_duplicate(struct maildir_sync_context *ctx,
				 const char *dir, const char *fname2)
{
	const char *fname1, *path1, *path2;
	const char *new_fname, *new_path;
	struct stat st1, st2;
	uoff_t size;

	fname1 = maildir_uidlist_sync_get_full_filename(ctx->uidlist_sync_ctx,
							fname2);
	i_assert(fname1 != NULL);

	path1 = t_strconcat(dir, "/", fname1, NULL);
	path2 = t_strconcat(dir, "/", fname2, NULL);

	if (stat(path1, &st1) < 0 || stat(path2, &st2) < 0) {
		/* most likely the files just don't exist anymore.
		   don't really care about other errors much. */
		return 0;
	}
	if (st1.st_ino == st2.st_ino &&
	    CMP_DEV_T(st1.st_dev, st2.st_dev)) {
		/* Files are the same. this means either a race condition
		   between stat() calls, or that the files were link()ed. */
		if (st1.st_nlink > 1 && st2.st_nlink == st1.st_nlink &&
		    st1.st_ctime == st2.st_ctime &&
		    st1.st_ctime < ioloop_time - DUPE_LINKS_DELETE_SECS) {
			/* The file has hard links and it hasn't had any
			   changes (such as renames) for a while, so this
			   isn't a race condition.

			   rename()ing one file on top of the other would fix
			   this safely, except POSIX decided that rename()
			   doesn't work that way. So we'll have unlink() one
			   and hope that another process didn't just decide to
			   unlink() the other (uidlist lock prevents this from
			   happening) */
			if (i_unlink(path2) == 0)
				i_warning("Unlinked a duplicate: %s", path2);
		}
		return 0;
	}

	new_fname = maildir_filename_generate();
	/* preserve S= and W= sizes if they're available.
	   (S=size is required for zlib plugin to work) */
	if (maildir_filename_get_size(fname2, MAILDIR_EXTRA_FILE_SIZE, &size)) {
		new_fname = t_strdup_printf("%s,%c=%"PRIuUOFF_T,
			new_fname, MAILDIR_EXTRA_FILE_SIZE, size);
	}
	if (maildir_filename_get_size(fname2, MAILDIR_EXTRA_VIRTUAL_SIZE, &size)) {
		new_fname = t_strdup_printf("%s,%c=%"PRIuUOFF_T,
			new_fname, MAILDIR_EXTRA_VIRTUAL_SIZE, size);
	}
	new_path = t_strconcat(mailbox_get_path(&ctx->mbox->box),
			       "/new/", new_fname, NULL);

	if (rename(path2, new_path) == 0)
		i_warning("Fixed a duplicate: %s -> %s", path2, new_fname);
	else if (errno != ENOENT) {
		mail_storage_set_critical(&ctx->mbox->storage->storage,
			"Couldn't fix a duplicate: rename(%s, %s) failed: %m",
			path2, new_path);
		return -1;
	}
	return 0;
}