bool sieve_binary_load_block
(struct sieve_binary_block *sblock)
{
	struct sieve_binary *sbin = sblock->sbin;
	unsigned int id = sblock->id;
	off_t offset = sblock->offset;
	const struct sieve_binary_block_header *header =
		LOAD_HEADER(sbin, &offset, const struct sieve_binary_block_header);

	if ( header == NULL ) {
		sieve_sys_error(sbin->svinst,
			"binary load: binary %s is corrupt: "
			"failed to read header of block %d", sbin->path, id);
		return FALSE;
	}

	if ( header->id != id ) {
		sieve_sys_error(sbin->svinst,
			"binary load: binary %s is corrupt: "
			"header of block %d has non-matching id %d",
			sbin->path, id, header->id);
		return FALSE;
	}

	sblock->data = sbin->file->load_buffer(sbin->file, &offset, header->size);
	if ( sblock->data == NULL ) {
		sieve_sys_error(sbin->svinst,
			"binary load: failed to read block %d of binary %s (size=%d)",
			id, sbin->path, header->size);
		return FALSE;
	}

	return TRUE;
}
static bool _read_block_index_record
(struct sieve_binary *sbin, off_t *offset, unsigned int id)
{
	const struct sieve_binary_block_index *record =
		LOAD_HEADER(sbin, offset, const struct sieve_binary_block_index);
	struct sieve_binary_block *block;

	if ( record == NULL ) {
		sieve_sys_error(sbin->svinst,
			"binary open: binary %s is corrupt: "
			"failed to load block index record %d", sbin->path, id);
		return FALSE;
	}

	if ( record->id != id ) {
		sieve_sys_error(sbin->svinst,
			"binary open: binary %s is corrupt: "
			"block index record %d has unexpected id %d", sbin->path, id, record->id);
		return FALSE;
	}

	block = sieve_binary_block_create_id(sbin, id);
	block->ext_index = record->ext_id;
	block->offset = record->offset;

	return TRUE;
}
static bool _file_lazy_read
(struct sieve_binary_file *file, off_t *offset, void *buffer, size_t size)
{
	struct sieve_instance *svinst = file->svinst;
	int ret;
	void *indata = buffer;
	size_t insize = size;

	*offset = SIEVE_BINARY_ALIGN(*offset);

	/* Seek to the correct position */
	if ( *offset != file->offset &&
		lseek(file->fd, *offset, SEEK_SET) == (off_t) -1 ) {
		sieve_sys_error(svinst, "binary read:"
			"failed to seek(fd, %lld, SEEK_SET) in binary %s: %m",
			(long long) *offset, file->path);
		return FALSE;
	}

	/* Read record into memory */
	while (insize > 0) {
		if ( (ret=read(file->fd, indata, insize)) <= 0 ) {
			if ( ret == 0 )
				sieve_sys_error(svinst,
					"binary read: binary %s is truncated (more data expected)",
					file->path);
			else
				sieve_sys_error(svinst,
					"binary read: failed to read from binary %s: %m", file->path);
			break;
		}

		indata = PTR_OFFSET(indata, ret);
		insize -= ret;
	}

	if ( insize != 0 ) {
		/* Failed to read the whole requested record */
		return FALSE;
	}

	*offset += size;
	file->offset = *offset;

	return TRUE;
}
static bool ext_ihave_binary_open
(const struct sieve_extension *ext, struct sieve_binary *sbin, void *context)
{
	struct sieve_instance *svinst = ext->svinst;
	struct ext_ihave_binary_context *binctx =
		(struct ext_ihave_binary_context *) context;
	struct sieve_binary_block *sblock;
	unsigned int i, count, block_id;
	sieve_size_t offset;

	sblock = sieve_binary_extension_get_block(sbin, ext);

	if ( sblock != NULL ) {
		binctx->block = sblock;
		block_id = sieve_binary_block_get_id(sblock);

		offset = 0;

		/* Read number of missing extensions to read subsequently */
		if ( !sieve_binary_read_unsigned(sblock, &offset, &count) ) {
			sieve_sys_error(svinst,
				"ihave: failed to read missing extension count "
				"from block %d of binary %s", block_id, sieve_binary_path(sbin));
			return FALSE;
		}

		/* Read dependencies */
		for ( i = 0; i < count; i++ ) {
			string_t *ext_name;
			const char *name;

			if ( !sieve_binary_read_string(sblock, &offset, &ext_name) ) {
				/* Binary is corrupt, recompile */
				sieve_sys_error(svinst,
					"ihave: failed to read missing extension name "
					"from block %d of binary %s", block_id, sieve_binary_path(sbin));
				return FALSE;
			}

			name = str_c(ext_name);
			array_append(&binctx->missing_extensions, &name, 1);
		}
	}

	return TRUE;
}
static bool _file_memory_load(struct sieve_binary_file *file)
{
	struct _file_memory *fmem = (struct _file_memory *) file;
	int ret;
	size_t size;
	void *indata;

	i_assert(file->fd > 0);

	/* Allocate memory buffer
	 */
	indata = p_malloc(file->pool, file->st.st_size);
	size = file->st.st_size;

	file->offset = 0;
	fmem->memory = indata;
	fmem->memory_size = file->st.st_size;

	/* Return to beginning of the file */
	if ( lseek(file->fd, 0, SEEK_SET) == (off_t) -1 ) {
		sieve_sys_error("failed to seek() in binary %s: %m", file->path);
		return FALSE;
	}

	/* Read the whole file into memory */
	while (size > 0) {
		if ( (ret=read(file->fd, indata, size)) <= 0 ) {
			sieve_sys_error("failed to read from binary %s: %m", file->path);
			break;
		}

		indata = PTR_OFFSET(indata, ret);
		size -= ret;
	}

	if ( size != 0 ) {
		/* Failed to read the whole file */
		return FALSE;
	}

	return TRUE;
}
static bool _sieve_extension_load(struct sieve_extension *ext)
{
	/* Call load handler */
	if ( ext->def != NULL && ext->def->load != NULL &&
		!ext->def->load(ext, &ext->context) ) {
		sieve_sys_error(ext->svinst,
			"failed to load '%s' extension support.", ext->def->name);
		return FALSE;
	}

	return TRUE;
}
static inline bool _save_skip
(struct sieve_binary *sbin, struct ostream *stream, size_t size)
{
	if ( (o_stream_seek(stream, stream->offset + size)) <= 0 ) {
		sieve_sys_error(sbin->svinst,
			"binary save: failed to skip output stream "
			"to position %"PRIuUOFF_T": %s", stream->offset + size,
			strerror(stream->stream_errno));
		return FALSE;
	}

	return TRUE;
}
void sieve_binary_file_close(struct sieve_binary_file **file)
{
	if ( (*file)->fd != -1 ) {
		if ( close((*file)->fd) < 0 ) {
			sieve_sys_error((*file)->svinst,
				"binary close: failed to close: close(fd=%s) failed: %m",
				(*file)->path);
		}
	}

	pool_unref(&(*file)->pool);

	*file = NULL;
}
static int _read_extensions(struct sieve_binary_block *sblock)
{
	struct sieve_binary *sbin = sblock->sbin;
	sieve_size_t offset = 0;
	unsigned int i, count;
	bool result = 1;

	if ( !sieve_binary_read_unsigned(sblock, &offset, &count) )
		return -1;

	for ( i = 0; result > 0 && i < count; i++ ) {
		T_BEGIN {
			string_t *extension;
			const struct sieve_extension *ext;
			unsigned int version;

			if ( sieve_binary_read_string(sblock, &offset, &extension) ) {
				ext = sieve_extension_get_by_name(sbin->svinst, str_c(extension));

				if ( ext == NULL ) {
					sieve_sys_error(sbin->svinst,
						"binary open: binary %s requires unknown extension `%s'",
						sbin->path, str_sanitize(str_c(extension), 128));
					result = 0;
				} else {
					struct sieve_binary_extension_reg *ereg = NULL;

					(void) sieve_binary_extension_register(sbin, ext, &ereg);
					if ( !sieve_binary_read_unsigned(sblock, &offset, &version) ||
						!sieve_binary_read_unsigned(sblock, &offset, &ereg->block_id) ) {
						result = -1;
					} else if ( !sieve_extension_version_is(ext, version) ) {
						sieve_sys_debug(sbin->svinst,
							"binary open: binary %s was compiled with different version "
							"of the `%s' extension (compiled v%d, expected v%d;"
							"automatically fixed when re-compiled)", sbin->path,
							sieve_extension_name(ext), version, sieve_extension_version(ext));
						result = 0;
					}
				}
			}	else
				result = -1;
		} T_END;
	}

	return result;
}
static inline bool _save_skip_aligned
(struct sieve_binary *sbin, struct ostream *stream, size_t size,
	uoff_t *offset)
{
	uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);

	if ( (o_stream_seek(stream, aligned_offset + size)) <= 0 ) {
		sieve_sys_error(sbin->svinst, "binary save: failed to skip output stream "
			"to position %"PRIuUOFF_T": %s", aligned_offset + size,
			strerror(stream->stream_errno));
		return FALSE;
	}

	if ( offset != NULL )
		*offset = aligned_offset;

	return TRUE;
}
/* FIXME: Is this even necessary for a file? */
static bool _save_full
(struct sieve_binary *sbin, struct ostream *stream, const void *data, size_t size)
{
	size_t bytes_left = size;
	const void *pdata = data;

	while ( bytes_left > 0 ) {
		ssize_t ret;

		if ( (ret=o_stream_send(stream, pdata, bytes_left)) <= 0 ) {
			sieve_sys_error(sbin->svinst,
				"binary save: failed to write %"PRIuSIZE_T" bytes "
				"to output stream: %s", bytes_left, strerror(stream->stream_errno));
			return FALSE;
		}

		pdata = PTR_OFFSET(pdata, ret);
		bytes_left -= ret;
	}

	return TRUE;
}
static bool _save_block_index_record
(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
{
	struct sieve_binary_block *block;
	struct sieve_binary_block_index header;

	block = sieve_binary_block_get(sbin, id);
	if ( block == NULL )
		return FALSE;

	header.id = id;
	header.size = buffer_get_used_size(block->data);
	header.ext_id = block->ext_index;
	header.offset = block->offset;

	if ( !_save_full(sbin, stream, &header, sizeof(header)) ) {
		sieve_sys_error(sbin->svinst,
			"binary save: failed to save block index header %d", id);
		return FALSE;
	}

	return TRUE;
}
Example #13
0
static int cmd_test_binary_operation_execute
(const struct sieve_runtime_env *renv, sieve_size_t *address)
{
	const struct sieve_operation *oprtn = renv->oprtn;
	string_t *binary_name = NULL;
	int ret;

	/* 
	 * Read operands 
	 */

	/* Binary Name */

	if ( (ret=sieve_opr_string_read(renv, address, "binary-name", &binary_name))
		<= 0 )
		return ret;

	/*
	 * Perform operation
	 */

	if ( sieve_operation_is(oprtn, test_binary_load_operation) ) {
		struct sieve_binary *sbin = testsuite_binary_load(str_c(binary_name));

		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
			sieve_runtime_trace(renv, 0, "testsuite: test_binary_load command");
			sieve_runtime_trace_descend(renv);
			sieve_runtime_trace(renv, 0, "load binary `%s'", str_c(binary_name));
		}

		if ( sbin != NULL ) {
			testsuite_script_set_binary(sbin);

			sieve_binary_unref(&sbin);
		} else {
			sieve_sys_error(testsuite_sieve_instance,
				"failed to load binary %s", str_c(binary_name));
			return SIEVE_EXEC_FAILURE;
		}

	} else if ( sieve_operation_is(oprtn, test_binary_save_operation) ) {
		struct sieve_binary *sbin = testsuite_script_get_binary();

		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
			sieve_runtime_trace(renv, 0, "testsuite: test_binary_save command");
			sieve_runtime_trace_descend(renv);
			sieve_runtime_trace(renv, 0, "save binary `%s'", str_c(binary_name));
		}

		if ( sbin != NULL ) 
			testsuite_binary_save(sbin, str_c(binary_name));
		else {
			sieve_sys_error(testsuite_sieve_instance,
				"no compiled binary to save as %s", str_c(binary_name));
			return SIEVE_EXEC_FAILURE;
		}
	} else {
		i_unreached();
	}

	return SIEVE_EXEC_OK;
}
static bool _sieve_binary_save
(struct sieve_binary *sbin, struct ostream *stream)
{
	struct sieve_binary_header header;
	struct sieve_binary_extension_reg *const *regs;
	struct sieve_binary_block *ext_block;
	unsigned int ext_count, blk_count, i;
	uoff_t block_index;

	blk_count = sieve_binary_block_count(sbin);

	/* Signal all extensions to finish generating their blocks */

	regs = array_get(&sbin->extensions, &ext_count);
	for ( i = 0; i < ext_count; i++ ) {
		const struct sieve_binary_extension *binext = regs[i]->binext;

		if ( binext != NULL && binext->binary_save != NULL )
			binext->binary_save(regs[i]->extension, sbin, regs[i]->context);
	}

	/* Create header */

	header.magic = SIEVE_BINARY_MAGIC;
	header.version_major = SIEVE_BINARY_VERSION_MAJOR;
	header.version_minor = SIEVE_BINARY_VERSION_MINOR;
	header.blocks = blk_count;

	if ( !_save_aligned(sbin, stream, &header, sizeof(header), NULL) ) {
		sieve_sys_error(sbin->svinst, "binary save: failed to save header");
		return FALSE;
	}

	/* Skip block index for now */

	if ( !_save_skip_aligned(sbin, stream,
		sizeof(struct sieve_binary_block_index) * blk_count, &block_index) )
		return FALSE;

	/* Create block containing all used extensions */

	ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
	i_assert( ext_block != NULL );
	sieve_binary_block_clear(ext_block);

	ext_count = array_count(&sbin->linked_extensions);
	sieve_binary_emit_unsigned(ext_block, ext_count);

	for ( i = 0; i < ext_count; i++ ) {
		struct sieve_binary_extension_reg * const *ext
			= array_idx(&sbin->linked_extensions, i);

		sieve_binary_emit_cstring
			(ext_block, sieve_extension_name((*ext)->extension));
		sieve_binary_emit_unsigned
			(ext_block, sieve_extension_version((*ext)->extension));
		sieve_binary_emit_unsigned(ext_block, (*ext)->block_id);
	}

	/* Save all blocks into the binary */

	for ( i = 0; i < blk_count; i++ ) {
		if ( !_save_block(sbin, stream, i) )
			return FALSE;
	}

	/* Create the block index */
	o_stream_seek(stream, block_index);
	for ( i = 0; i < blk_count; i++ ) {
		if ( !_save_block_index_record(sbin, stream, i) )
			return FALSE;
	}

	return TRUE;
}
int sieve_binary_save
(struct sieve_binary *sbin, const char *path, bool update, mode_t save_mode,
	enum sieve_error *error_r)
{
	int result, fd;
	string_t *temp_path;
	struct ostream *stream;

	if ( error_r != NULL )
		*error_r = SIEVE_ERROR_NONE;

	/* Check whether saving is necessary */
	if ( !update && sbin->path != NULL && strcmp(sbin->path, path) == 0 ) {
		if ( sbin->svinst->debug ) {
			sieve_sys_debug(sbin->svinst, "binary save: not saving binary %s, "
				"because it is already stored", path);
		}
		return 0;
	}

	/* Open it as temp file first, as not to overwrite an existing just yet */
	temp_path = t_str_new(256);
	str_append(temp_path, path);
	str_append_c(temp_path, '.');
	fd = safe_mkstemp_hostpid(temp_path, save_mode, (uid_t)-1, (gid_t)-1);
	if ( fd < 0 ) {
		if ( errno == EACCES ) {
			sieve_sys_error(sbin->svinst,
				"binary save: failed to create temporary file: %s",
				eacces_error_get_creating("open", str_c(temp_path)));
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_NO_PERMISSION;
		} else {
			sieve_sys_error(sbin->svinst,
				"binary save: failed to create temporary file: open(%s) failed: %m",
				str_c(temp_path));
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_TEMP_FAILURE;
		}
		return -1;
	}

	/* Save binary */
	result = 1;
	stream = o_stream_create_fd(fd, 0, FALSE);
	if ( !_sieve_binary_save(sbin, stream) ) {
		result = -1;
		if ( error_r != NULL )
			*error_r = SIEVE_ERROR_TEMP_FAILURE;
	}
	o_stream_destroy(&stream);

	/* Close saved binary */
	if ( close(fd) < 0 ) {
		sieve_sys_error(sbin->svinst,
			"binary save: failed to close temporary file: "
			"close(fd=%s) failed: %m", str_c(temp_path));
	}

	/* Replace any original binary atomically */
	if ( result && (rename(str_c(temp_path), path) < 0) ) {
		if ( errno == EACCES ) {
			sieve_sys_error(sbin->svinst, "binary save: failed to save binary: %s",
				eacces_error_get_creating("rename", path));
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_NO_PERMISSION;
		} else {
			sieve_sys_error(sbin->svinst, "binary save: failed to save binary: "
				"rename(%s, %s) failed: %m", str_c(temp_path), path);
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_TEMP_FAILURE;
		}
		result = -1;
	}

	if ( result < 0 ) {
		/* Get rid of temp output (if any) */
		if ( unlink(str_c(temp_path)) < 0 && errno != ENOENT ) {
			sieve_sys_error(sbin->svinst,
				"binary save: failed to clean up after error: unlink(%s) failed: %m",
				str_c(temp_path));
		}
	} else {
		if ( sbin->path == NULL ) {
			sbin->path = p_strdup(sbin->pool, path);
		}
	}

	return result;
}
bool sieve_binary_file_open
(struct sieve_binary_file *file,
	struct sieve_instance *svinst, const char *path, enum sieve_error *error_r)
{
	int fd;
	bool result = TRUE;
	struct stat st;

	if ( error_r != NULL )
		*error_r = SIEVE_ERROR_NONE;

	if ( (fd=open(path, O_RDONLY)) < 0 ) {
		switch ( errno ) {
		case ENOENT:
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_NOT_FOUND;
			break;
		case EACCES:
			sieve_sys_error(svinst, "binary open: failed to open: %s",
				eacces_error_get("open", path));
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_NO_PERMISSION;
			break;
		default:
			sieve_sys_error(svinst, "binary open: failed to open: "
				"open(%s) failed: %m", path);
			if ( error_r != NULL )
				*error_r = SIEVE_ERROR_TEMP_FAILURE;
			break;
		}
		return FALSE;
	}

	if ( fstat(fd, &st) < 0 ) {
		if ( errno != ENOENT ) {
			sieve_sys_error(svinst,
				"binary open: fstat(fd=%s) failed: %m", path);
		}
		result = FALSE;
	}

	if ( result && !S_ISREG(st.st_mode) ) {
		sieve_sys_error(svinst,
			"binary open: %s is not a regular file", path);
		result = FALSE;
	}

	if ( !result )	{
		if ( close(fd) < 0 ) {
			sieve_sys_error(svinst,
				"binary open: close(fd=%s) failed after error: %m", path);
		}
		return FALSE;
	}

	file->svinst = svinst;
	file->fd = fd;
	file->st = st;

	return TRUE;
}
static bool _sieve_binary_open(struct sieve_binary *sbin)
{
	bool result = TRUE;
	off_t offset = 0;
	const struct sieve_binary_header *header;
	struct sieve_binary_block *ext_block;
	unsigned int i, blk_count;
	int ret;

	/* Verify header */

	T_BEGIN {
		header = LOAD_HEADER(sbin, &offset, const struct sieve_binary_header);
		/* Check header presence */
		if ( header == NULL ) {
			sieve_sys_error(sbin->svinst,
				"binary_open: file %s is not large enough to contain the header.",
				sbin->path);
			result = FALSE;

		/* Check header validity */
		} else if ( header->magic != SIEVE_BINARY_MAGIC ) {
			if ( header->magic != SIEVE_BINARY_MAGIC_OTHER_ENDIAN )
				sieve_sys_error(sbin->svinst,
					"binary_open: binary %s has corrupted header "
					"(0x%08x) or it is not a Sieve binary", sbin->path, header->magic);
			else if ( sbin->svinst->debug )
				sieve_sys_debug(sbin->svinst,
					"binary open: binary %s stored with in different endian format "
					"(automatically fixed when re-compiled)",
					sbin->path);
			result = FALSE;

		/* Check binary version */
		} else if ( result && (
		  header->version_major != SIEVE_BINARY_VERSION_MAJOR ||
			header->version_minor != SIEVE_BINARY_VERSION_MINOR ) ) {

			/* Binary is of different version. Caller will have to recompile */

			if ( sbin->svinst->debug ) {
				sieve_sys_debug(sbin->svinst,
					"binary open: binary %s stored with different binary version %d.%d "
					"(!= %d.%d; automatically fixed when re-compiled)", sbin->path,
					(int) header->version_major, header->version_minor,
					SIEVE_BINARY_VERSION_MAJOR, SIEVE_BINARY_VERSION_MINOR);
			}
			result = FALSE;

		/* Check block content */
		} else if ( result && header->blocks == 0 ) {
			sieve_sys_error(sbin->svinst,
				"binary open: binary %s is corrupt: it contains no blocks",
				sbin->path);
			result = FALSE;

		/* Valid */
		} else {
			blk_count = header->blocks;
		}
	} T_END;

	if ( !result ) return FALSE;

	/* Load block index */

	for ( i = 0; i < blk_count && result; i++ ) {
		T_BEGIN {
			if ( !_read_block_index_record(sbin, &offset, i) ) {
				result = FALSE;
			}
		} T_END;
	}

	if ( !result ) return FALSE;

	/* Load extensions used by this binary */

	T_BEGIN {
		ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
		if ( ext_block == NULL ) {
			result = FALSE;
		} else if ( (ret=_read_extensions(ext_block)) <= 0 ) {
			if ( ret < 0 ) {
				sieve_sys_error(sbin->svinst,
					"binary open: binary %s is corrupt: failed to load extension block",
					sbin->path);
			}
			result = FALSE;
		}
	} T_END;

	return result;
}