static gboolean
rspamd_fuzzy_backend_redis_try_ucl (struct rspamd_fuzzy_backend_redis *backend,
		const ucl_object_t *obj,
		struct rspamd_config *cfg)
{
	const ucl_object_t *elt, *relt;

	elt = ucl_object_lookup_any (obj, "read_servers", "servers", NULL);

	if (elt == NULL) {
		return FALSE;
	}

	backend->read_servers = rspamd_upstreams_create (cfg->ups_ctx);
	if (!rspamd_upstreams_from_ucl (backend->read_servers, elt,
			REDIS_DEFAULT_PORT, NULL)) {
		msg_err_config ("cannot get read servers configuration");
		return FALSE;
	}

	relt = elt;

	elt = ucl_object_lookup (obj, "write_servers");
	if (elt == NULL) {
		/* Use read servers as write ones */
		g_assert (relt != NULL);
		backend->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (backend->write_servers, relt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err_config ("cannot get write servers configuration");
			return FALSE;
		}
	}
	else {
		backend->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (backend->write_servers, elt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err_config ("cannot get write servers configuration");
			rspamd_upstreams_destroy (backend->write_servers);
			backend->write_servers = NULL;
		}
	}

	elt = ucl_object_lookup (obj, "prefix");
	if (elt == NULL || ucl_object_type (elt) != UCL_STRING) {
		backend->redis_object = REDIS_DEFAULT_OBJECT;
	}
	else {
		backend->redis_object = ucl_object_tostring (elt);
	}

	elt = ucl_object_lookup (obj, "timeout");
	if (elt) {
		backend->timeout = ucl_object_todouble (elt);
	}
	else {
		backend->timeout = REDIS_DEFAULT_TIMEOUT;
	}

	elt = ucl_object_lookup (obj, "password");
	if (elt) {
		backend->password = ucl_object_tostring (elt);
	}
	else {
		backend->password = NULL;
	}

	elt = ucl_object_lookup_any (obj, "db", "database", "dbname", NULL);
	if (elt) {
		backend->dbname = ucl_object_tostring (elt);
	}
	else {
		backend->dbname = NULL;
	}

	return TRUE;
}
void*
rspamd_fuzzy_backend_init_redis (struct rspamd_fuzzy_backend *bk,
		const ucl_object_t *obj, struct rspamd_config *cfg, GError **err)
{
	struct rspamd_fuzzy_backend_redis *backend;
	const ucl_object_t *elt;
	gboolean ret = FALSE;
	guchar id_hash[rspamd_cryptobox_HASHBYTES];
	rspamd_cryptobox_hash_state_t st;

	backend = g_slice_alloc0 (sizeof (*backend));

	backend->timeout = REDIS_DEFAULT_TIMEOUT;
	backend->redis_object = REDIS_DEFAULT_OBJECT;

	ret = rspamd_fuzzy_backend_redis_try_ucl (backend, obj, cfg);

	/* Now try global redis settings */
	if (!ret) {
		elt = ucl_object_lookup (cfg->rcl_obj, "redis");

		if (elt) {
			const ucl_object_t *specific_obj;

			specific_obj = ucl_object_lookup_any (elt, "fuzzy", "fuzzy_storage",
					NULL);

			if (specific_obj) {
				ret = rspamd_fuzzy_backend_redis_try_ucl (backend, specific_obj,
						cfg);
			}
			else {
				ret = rspamd_fuzzy_backend_redis_try_ucl (backend, elt, cfg);
			}
		}
	}

	if (!ret) {
		msg_err_config ("cannot init redis backend for fuzzy storage");
		g_slice_free1 (sizeof (*backend), backend);
		return NULL;
	}

	REF_INIT_RETAIN (backend, rspamd_fuzzy_backend_redis_dtor);
	backend->pool = cfg->redis_pool;
	rspamd_cryptobox_hash_init (&st, NULL, 0);
	rspamd_cryptobox_hash_update (&st, backend->redis_object,
			strlen (backend->redis_object));

	if (backend->dbname) {
		rspamd_cryptobox_hash_update (&st, backend->dbname,
				strlen (backend->dbname));
	}

	if (backend->password) {
		rspamd_cryptobox_hash_update (&st, backend->password,
				strlen (backend->password));
	}

	rspamd_cryptobox_hash_final (&st, id_hash);
	backend->id = rspamd_encode_base32 (id_hash, sizeof (id_hash));

	return backend;
}
Beispiel #3
0
struct rspamd_map*
rspamd_map_add_from_ucl (struct rspamd_config *cfg,
	const ucl_object_t *obj,
	const gchar *description,
	map_cb_t read_callback,
	map_fin_cb_t fin_callback,
	void **user_data)
{
	ucl_object_iter_t it = NULL;
	const ucl_object_t *cur, *elt;
	struct rspamd_map *map;
	struct rspamd_map_backend *bk;

	g_assert (obj != NULL);

	if (ucl_object_type (obj) == UCL_STRING) {
		/* Just a plain string */
		return rspamd_map_add (cfg, ucl_object_tostring (obj), NULL,
				read_callback, fin_callback, user_data);
	}

	map = g_slice_alloc0 (sizeof (struct rspamd_map));
	map->read_callback = read_callback;
	map->fin_callback = fin_callback;
	map->user_data = user_data;
	map->cfg = cfg;
	map->id = g_random_int ();
	map->locked =
			rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (gint));
	map->cache =
				rspamd_mempool_alloc0_shared (cfg->cfg_pool, sizeof (*map->cache));
	map->backends = g_ptr_array_new ();
	map->poll_timeout = cfg->map_timeout;

	if (description) {
		map->description = g_strdup (description);
	}

	if (ucl_object_type (obj) == UCL_ARRAY) {
		/* Add array of maps as multiple backends */
		while ((cur = ucl_object_iterate (obj, &it, true)) != NULL) {
			if (ucl_object_type (cur) == UCL_STRING) {
				bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (cur));

				if (bk != NULL) {
					g_ptr_array_add (map->backends, bk);

					if (!map->name) {
						map->name = g_strdup (ucl_object_tostring (cur));
					}
				}
			}
			else {
				msg_err_config ("bad map element type: %s",
						ucl_object_type_to_string (ucl_object_type (cur)));
			}
		}

		if (map->backends->len == 0) {
			msg_err_config ("map has no urls to be loaded");
			goto err;
		}
	}
	else if (ucl_object_type (obj) == UCL_OBJECT) {
		elt = ucl_object_lookup (obj, "name");
		if (elt && ucl_object_type (elt) == UCL_STRING) {
			map->name = g_strdup (ucl_object_tostring (elt));
		}

		elt = ucl_object_lookup (obj, "description");
		if (elt && ucl_object_type (elt) == UCL_STRING) {
			if (map->description) {
				g_free (map->description);
			}

			map->description = g_strdup (ucl_object_tostring (elt));
		}

		elt = ucl_object_lookup_any (obj, "timeout", "poll", "poll_time",
				"watch_interval", NULL);
		if (elt) {
			map->poll_timeout = ucl_object_todouble (elt);
		}

		elt = ucl_object_lookup_any (obj, "upstreams", "url", "urls", NULL);
		if (elt == NULL) {
			msg_err_config ("map has no urls to be loaded");
			goto err;
		}

		if (ucl_object_type (obj) == UCL_ARRAY) {
			/* Add array of maps as multiple backends */
			while ((cur = ucl_object_iterate (elt, &it, true)) != NULL) {
				if (ucl_object_type (cur) == UCL_STRING) {
					bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (cur));

					if (bk != NULL) {
						g_ptr_array_add (map->backends, bk);

						if (!map->name) {
							map->name = g_strdup (ucl_object_tostring (cur));
						}
					}
				}
				else {
					msg_err_config ("bad map element type: %s",
							ucl_object_type_to_string (ucl_object_type (cur)));
					goto err;
				}
			}

			if (map->backends->len == 0) {
				msg_err_config ("map has no urls to be loaded");
				goto err;
			}
		}
		else if (ucl_object_type (elt) == UCL_STRING) {
			bk = rspamd_map_parse_backend (cfg, ucl_object_tostring (elt));

			if (bk != NULL) {
				g_ptr_array_add (map->backends, bk);

				if (!map->name) {
					map->name = g_strdup (ucl_object_tostring (cur));
				}
			}
		}

		if (map->backends->len == 0) {
			msg_err_config ("map has no urls to be loaded");
			goto err;
		}
	}

	rspamd_map_calculate_hash (map);
	msg_info_map ("added map from ucl");

	cfg->maps = g_list_prepend (cfg->maps, map);

	return map;

err:
	g_ptr_array_free (map->backends, TRUE);
	g_free (map->name);
	g_free (map->description);
	g_slice_free1 (sizeof (*map), map);

	return NULL;
}
Beispiel #4
0
static gboolean
rspamd_redis_cache_try_ucl (struct rspamd_redis_cache_ctx *cache_ctx,
		const ucl_object_t *obj,
		struct rspamd_config *cfg,
		const gchar *symbol)
{
	const ucl_object_t *elt, *relt;

	elt = ucl_object_lookup_any (obj, "read_servers", "servers", NULL);

	if (elt == NULL) {
		return FALSE;
	}

	cache_ctx->read_servers = rspamd_upstreams_create (cfg->ups_ctx);
	if (!rspamd_upstreams_from_ucl (cache_ctx->read_servers, elt,
			REDIS_DEFAULT_PORT, NULL)) {
		msg_err ("statfile %s cannot get read servers configuration",
				symbol);
		return FALSE;
	}

	relt = elt;

	elt = ucl_object_lookup (obj, "write_servers");
	if (elt == NULL) {
		/* Use read servers as write ones */
		g_assert (relt != NULL);
		cache_ctx->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (cache_ctx->write_servers, relt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err ("statfile %s cannot get write servers configuration",
					symbol);
			return FALSE;
		}
	}
	else {
		cache_ctx->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (cache_ctx->write_servers, elt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err ("statfile %s cannot get write servers configuration",
					symbol);
			rspamd_upstreams_destroy (cache_ctx->write_servers);
			cache_ctx->write_servers = NULL;
		}
	}


	elt = ucl_object_lookup (obj, "timeout");
	if (elt) {
		cache_ctx->timeout = ucl_object_todouble (elt);
	}
	else {
		cache_ctx->timeout = REDIS_DEFAULT_TIMEOUT;
	}

	elt = ucl_object_lookup (obj, "password");
	if (elt) {
		cache_ctx->password = ucl_object_tostring (elt);
	}
	else {
		cache_ctx->password = NULL;
	}

	elt = ucl_object_lookup_any (obj, "db", "database", "dbname", NULL);
	if (elt) {
		cache_ctx->dbname = ucl_object_tostring (elt);
	}
	else {
		cache_ctx->dbname = NULL;
	}

	elt = ucl_object_lookup_any (obj, "cache_key", "key", NULL);
	if (elt == NULL || ucl_object_type (elt) != UCL_STRING) {
		cache_ctx->redis_object = DEFAULT_REDIS_KEY;
	}
	else {
		cache_ctx->redis_object = ucl_object_tostring (elt);
	}

	return TRUE;
}
Beispiel #5
0
static gboolean
rspamd_redis_try_ucl (struct redis_stat_ctx *backend,
		const ucl_object_t *obj,
		struct rspamd_config *cfg,
		const gchar *symbol)
{
	const ucl_object_t *elt, *relt, *users_enabled;
	const gchar *lua_script;

	elt = ucl_object_lookup_any (obj, "read_servers", "servers", NULL);

	if (elt == NULL) {
		return FALSE;
	}

	backend->read_servers = rspamd_upstreams_create (cfg->ups_ctx);
	if (!rspamd_upstreams_from_ucl (backend->read_servers, elt,
			REDIS_DEFAULT_PORT, NULL)) {
		msg_err ("statfile %s cannot get read servers configuration",
				symbol);
		return FALSE;
	}

	relt = elt;

	elt = ucl_object_lookup (obj, "write_servers");
	if (elt == NULL) {
		/* Use read servers as write ones */
		g_assert (relt != NULL);
		backend->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (backend->write_servers, relt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err ("statfile %s cannot get write servers configuration",
					symbol);
			return FALSE;
		}
	}
	else {
		backend->write_servers = rspamd_upstreams_create (cfg->ups_ctx);
		if (!rspamd_upstreams_from_ucl (backend->write_servers, elt,
				REDIS_DEFAULT_PORT, NULL)) {
			msg_err ("statfile %s cannot get write servers configuration",
					symbol);
			rspamd_upstreams_destroy (backend->write_servers);
			backend->write_servers = NULL;
		}
	}

	elt = ucl_object_lookup (obj, "prefix");
	if (elt == NULL || ucl_object_type (elt) != UCL_STRING) {
		/* Default non-users statistics */
		backend->redis_object = REDIS_DEFAULT_OBJECT;

		/*
		 * Make redis backend compatible with sqlite3 backend in users settings
		 */
		users_enabled = ucl_object_lookup_any (obj, "per_user",
				"users_enabled", NULL);

		if (users_enabled != NULL) {
			if (ucl_object_type (users_enabled) == UCL_BOOLEAN) {
				backend->enable_users = ucl_object_toboolean (users_enabled);
				backend->cbref_user = -1;

				if (backend->enable_users) {
					backend->redis_object = REDIS_DEFAULT_USERS_OBJECT;
				}
			}
			else if (ucl_object_type (users_enabled) == UCL_STRING) {
				lua_script = ucl_object_tostring (users_enabled);

				if (luaL_dostring (cfg->lua_state, lua_script) != 0) {
					msg_err_config ("cannot execute lua script for users "
							"extraction: %s", lua_tostring (cfg->lua_state, -1));
				}
				else {
					if (lua_type (cfg->lua_state, -1) == LUA_TFUNCTION) {
						backend->enable_users = TRUE;
						backend->cbref_user = luaL_ref (cfg->lua_state,
								LUA_REGISTRYINDEX);
					}
					else {
						msg_err_config ("lua script must return "
								"function(task) and not %s",
								lua_typename (cfg->lua_state, lua_type (
										cfg->lua_state, -1)));
					}
				}
			}
		}
		else {
			backend->enable_users = FALSE;
		}
	}
	else {
		/* XXX: sanity check */
		backend->redis_object = ucl_object_tostring (elt);
	}

	elt = ucl_object_lookup (obj, "timeout");
	if (elt) {
		backend->timeout = ucl_object_todouble (elt);
	}
	else {
		backend->timeout = REDIS_DEFAULT_TIMEOUT;
	}

	elt = ucl_object_lookup (obj, "password");
	if (elt) {
		backend->password = ucl_object_tostring (elt);
	}
	else {
		backend->password = NULL;
	}

	elt = ucl_object_lookup_any (obj, "db", "database", "dbname", NULL);
	if (elt) {
		backend->dbname = ucl_object_tostring (elt);
	}
	else {
		backend->dbname = NULL;
	}

	return TRUE;
}
Beispiel #6
0
int
main (int argc, char **argv)
{
	ucl_object_t *obj, *cur, *ar, *ar1, *ref, *test_obj, *comments;
	ucl_object_iter_t it;
	const ucl_object_t *found, *it_obj, *test;
	struct ucl_emitter_functions *fn;
	FILE *out;
	unsigned char *emitted;
	const char *fname_out = NULL;
	struct ucl_parser *parser;
	int ret = 0;

	switch (argc) {
	case 2:
		fname_out = argv[1];
		break;
	}


	if (fname_out != NULL) {
		out = fopen (fname_out, "w");
		if (out == NULL) {
			exit (-errno);
		}
	}
	else {
		out = stdout;
	}

	obj = ucl_object_typed_new (UCL_OBJECT);

	/* Keys replacing */
	cur = ucl_object_fromstring_common ("value1", 0, UCL_STRING_TRIM);
	ucl_object_insert_key (obj, cur, "key0", 0, false);
	cur = ucl_object_fromdouble (0.1);
	assert (ucl_object_replace_key (obj, cur, "key0", 0, false));

	/* Create some strings */
	cur = ucl_object_fromstring_common ("  test string    ", 0, UCL_STRING_TRIM);
	ucl_object_insert_key (obj, cur, "key1", 0, false);
	cur = ucl_object_fromstring_common ("  test \nstring\n\r\n\b\t\f\\\"    ", 0,
			UCL_STRING_TRIM | UCL_STRING_ESCAPE);
	ucl_object_insert_key (obj, cur, "key2", 0, false);
	cur = ucl_object_fromstring_common ("  test string    \n", 0, 0);
	ucl_object_insert_key (obj, cur, "key3", 0, false);
	/* Array of numbers */
	ar = ucl_object_typed_new (UCL_ARRAY);
	cur = ucl_object_fromint (10);
	ucl_array_append (ar, cur);
	assert (ucl_array_index_of (ar, cur) == 0);
	cur = ucl_object_fromdouble (10.1);
	ucl_array_append (ar, cur);
	assert (ucl_array_index_of (ar, cur) == 1);
	cur = ucl_object_fromdouble (9.999);
	ucl_array_prepend (ar, cur);
	assert (ucl_array_index_of (ar, cur) == 0);

	ar1 = ucl_object_copy (ar);
	cur = ucl_object_fromstring ("abc");
	ucl_array_prepend (ar1, cur);
	cur = ucl_object_fromstring ("cde");
	ucl_array_prepend (ar1, cur);
	cur = ucl_object_fromstring ("аАаБаВ"); /* UTF8 */
	ucl_array_prepend (ar1, cur);
	cur = ucl_object_fromstring ("а•аБаВ"); /* UTF8 */
	ucl_array_prepend (ar1, cur);
/*
 * This is ususally broken or fragile as utf collate is far from perfect
	cur = ucl_object_fromstring ("б‘аБаВ");
	ucl_array_prepend (ar1, cur);
	cur = ucl_object_fromstring ("ааБаВ"); // hello to @bapt
	ucl_array_prepend (ar1, cur);
*/
	cur = ucl_object_fromstring ("№Ÿ˜Ž"); /* everybody likes emoji in the code */
	ucl_array_prepend (ar1, cur);

	ucl_object_array_sort (ar1, ucl_object_compare_qsort);

	/* Removing from an array */
	cur = ucl_object_fromdouble (1.0);
	ucl_array_append (ar, cur);
	cur = ucl_array_delete (ar, cur);
	assert (ucl_object_todouble (cur) == 1.0);
	ucl_object_unref (cur);
	cur = ucl_object_fromdouble (2.0);
	ucl_array_append (ar, cur);
	cur = ucl_array_pop_last (ar);
	assert (ucl_object_todouble (cur) == 2.0);
	ucl_object_unref (cur);
	cur = ucl_object_fromdouble (3.0);
	ucl_array_prepend (ar, cur);
	cur = ucl_array_pop_first (ar);
	assert (ucl_object_todouble (cur) == 3.0);
	ucl_object_unref (cur);

	ucl_object_insert_key (obj, ar, "key4", 0, false);
	cur = ucl_object_frombool (true);
	/* Ref object to test refcounts */
	ref = ucl_object_ref (cur);
	ucl_object_insert_key (obj, cur, "key4", 0, false);
	/* Empty strings */
	cur = ucl_object_fromstring_common ("      ", 0, UCL_STRING_TRIM);
	ucl_object_insert_key (obj, cur, "key5", 0, false);
	cur = ucl_object_fromstring_common ("", 0, UCL_STRING_ESCAPE);
	ucl_object_insert_key (obj, cur, "key6", 0, false);
	cur = ucl_object_fromstring_common ("   \n", 0, UCL_STRING_ESCAPE);
	ucl_object_insert_key (obj, cur, "key7", 0, false);
	/* Numbers and booleans */
	cur = ucl_object_fromstring_common ("1mb", 0, UCL_STRING_ESCAPE | UCL_STRING_PARSE);
	ucl_object_insert_key (obj, cur, "key8", 0, false);
	cur = ucl_object_fromstring_common ("3.14", 0, UCL_STRING_PARSE);
	ucl_object_insert_key (obj, cur, "key9", 0, false);
	cur = ucl_object_fromstring_common ("true", 0, UCL_STRING_PARSE);
	ucl_object_insert_key (obj, cur, "key10", 0, false);
	cur = ucl_object_fromstring_common ("  off  ", 0, UCL_STRING_PARSE | UCL_STRING_TRIM);
	ucl_object_insert_key (obj, cur, "key11", 0, false);
	cur = ucl_object_fromstring_common ("*****@*****.**", 0, UCL_STRING_PARSE_INT);
	ucl_object_insert_key (obj, cur, "key12", 0, false);
	cur = ucl_object_fromstring_common ("#test", 0, UCL_STRING_PARSE_INT);
	ucl_object_insert_key (obj, cur, "key13", 0, false);
	cur = ucl_object_frombool (true);
	ucl_object_insert_key (obj, cur, "k=3", 0, false);
	ucl_object_insert_key (obj, ar1, "key14", 0, false);
	cur = ucl_object_new_userdata (ud_dtor, ud_emit, NULL);
	ucl_object_insert_key (obj, cur, "key15", 0, false);

	/* More tests for keys */
	cur = ucl_object_fromlstring ("test", 3);
	ucl_object_insert_key (obj, cur, "key16", 0, false);
	test = ucl_object_lookup_any (obj, "key100", "key200", "key300", "key16", NULL);
	assert (test == cur);
	test = ucl_object_lookup_len (obj, "key160", 5);
	assert (test == cur);
	cur = ucl_object_pop_key (obj, "key16");
	assert (test == cur);
	test = ucl_object_pop_key (obj, "key16");
	assert (test == NULL);
	test = ucl_object_lookup_len (obj, "key160", 5);
	assert (test == NULL);
	/* Objects merging tests */
	test_obj = ucl_object_new_full (UCL_OBJECT, 2);
	ucl_object_insert_key (test_obj, cur, "key16", 0, true);
	ucl_object_merge (obj, test_obj, true);
	ucl_object_unref (test_obj);
	/* Array merging test */
	test_obj = ucl_object_new_full (UCL_ARRAY, 3);
	ucl_array_append (test_obj, ucl_object_fromstring ("test"));
	ucl_array_merge (test_obj, ar1, false);
	ucl_object_insert_key (obj, test_obj, "key17", 0, true);
	/* Object deletion */
	cur = ucl_object_fromstring ("test");
	ucl_object_insert_key (obj, cur, "key18", 0, true);
	assert (ucl_object_delete_key (obj, "key18"));
	assert (!ucl_object_delete_key (obj, "key18"));
	cur = ucl_object_fromlstring ("test", 4);
	ucl_object_insert_key (obj, cur, "key18\0\0", 7, true);
	assert (ucl_object_lookup_len (obj, "key18\0\0", 7) == cur);
	assert (ucl_object_lookup (obj, "key18") == NULL);
	assert (ucl_object_lookup_len (obj, "key18\0\1", 7) == NULL);
	assert (ucl_object_delete_keyl (obj, "key18\0\0", 7));

	/* Comments */

	comments = ucl_object_typed_new (UCL_OBJECT);
	found = ucl_object_lookup (obj, "key17");
	test = ucl_object_lookup (obj, "key16");
	ucl_comments_add (comments, found, "# test comment");
	assert (ucl_comments_find (comments, found) != NULL);
	assert (ucl_comments_find (comments, test) == NULL);
	ucl_comments_move (comments, found, test);
	assert (ucl_comments_find (comments, found) == NULL);
	assert (ucl_comments_find (comments, test) != NULL);

	/* Array replace */
	ar1 = ucl_object_typed_new (UCL_ARRAY);
	cur = ucl_object_fromstring ("test");
	cur = ucl_elt_append (cur, ucl_object_fromstring ("test1"));
	ucl_array_append (ar1, cur);
	test = ucl_array_replace_index (ar1, ucl_object_fromstring ("test2"), 0);
	assert (test == cur);

	/* Try to find using path */
	/* Should exist */
	found = ucl_object_lookup_path (obj, "key4.1");
	assert (found != NULL && ucl_object_toint (found) == 10);
	/* . should be ignored */
	found = ucl_object_lookup_path (obj, ".key4.1");
	assert (found != NULL && ucl_object_toint (found) == 10);
	/* moar dots... */
	found = ucl_object_lookup_path (obj, ".key4........1...");
	assert (found != NULL && ucl_object_toint (found) == 10);
	/* No such index */
	found = ucl_object_lookup_path (obj, ".key4.3");
	assert (found == NULL);
	/* No such key */
	found = ucl_object_lookup_path (obj, "key9..key1");
	assert (found == NULL);

	/* Test iteration */
	it = ucl_object_iterate_new (obj);
	it_obj = ucl_object_iterate_safe (it, true);
	/* key0 = 0.1 */
	assert (ucl_object_type (it_obj) == UCL_FLOAT);
	it_obj = ucl_object_iterate_safe (it, true);
	/* key1 = "" */
	assert (ucl_object_type (it_obj) == UCL_STRING);
	it_obj = ucl_object_iterate_safe (it, true);
	/* key2 = "" */
	assert (ucl_object_type (it_obj) == UCL_STRING);
	it_obj = ucl_object_iterate_safe (it, true);
	/* key3 = "" */
	assert (ucl_object_type (it_obj) == UCL_STRING);
	it_obj = ucl_object_iterate_safe (it, true);
	/* key4 = ([float, int, float], boolean) */
	ucl_object_iterate_reset (it, it_obj);
	it_obj = ucl_object_iterate_safe (it, true);
	assert (ucl_object_type (it_obj) == UCL_FLOAT);
	it_obj = ucl_object_iterate_safe (it, true);
	assert (ucl_object_type (it_obj) == UCL_INT);
	it_obj = ucl_object_iterate_safe (it, true);
	assert (ucl_object_type (it_obj) == UCL_FLOAT);
	it_obj = ucl_object_iterate_safe (it, true);
	assert (ucl_object_type (it_obj) == UCL_BOOLEAN);
	ucl_object_iterate_free (it);

	fn = ucl_object_emit_memory_funcs (&emitted);
	assert (ucl_object_emit_full (obj, UCL_EMIT_CONFIG, fn, comments));
	fprintf (out, "%s\n", emitted);
	ucl_object_emit_funcs_free (fn);
	ucl_object_unref (obj);
	ucl_object_unref (comments);

	parser = ucl_parser_new (UCL_PARSER_NO_IMPLICIT_ARRAYS);

	if (ucl_parser_add_chunk_full (parser, emitted, strlen (emitted),
			3, UCL_DUPLICATE_ERROR, UCL_PARSE_UCL)) {
		/* Should fail due to duplicate */
		assert (0);
	}
	else {
		assert (ucl_parser_get_error (parser) != NULL);
		ucl_parser_clear_error (parser);
		ucl_parser_free (parser);
		parser = ucl_parser_new (0);
		ucl_parser_add_chunk_full (parser, emitted, strlen (emitted),
					3, UCL_DUPLICATE_MERGE, UCL_PARSE_UCL);
	}

	assert (ucl_parser_get_column (parser) == 0);
	assert (ucl_parser_get_linenum (parser) != 0);
	ucl_parser_clear_error (parser);
	assert (ucl_parser_get_error_code (parser) == 0);
	obj = ucl_parser_get_object (parser);
	ucl_parser_free (parser);
	ucl_object_free (obj);

	if (emitted != NULL) {
		free (emitted);
	}
	fclose (out);

	/* Ref should still be accessible */
	ref->value.iv = 100500;
	ucl_object_unref (ref);

	return ret;
}