diff --git a/src/lib-sieve/util/Makefile.am b/src/lib-sieve/util/Makefile.am index 74a269202..42aef0773 100644 --- a/src/lib-sieve/util/Makefile.am +++ b/src/lib-sieve/util/Makefile.am @@ -21,3 +21,25 @@ headers = \ pkginc_libdir=$(dovecot_pkgincludedir)/sieve pkginc_lib_HEADERS = $(headers) + +test_programs = \ + test-realpath + +noinst_PROGRAMS = $(test_programs) + +test_libs = \ + libsieve_util.la \ + $(LIBDOVECOT) +test_deps = \ + libsieve_util.la \ + $(LIBDOVECOT_DEPS) + +test_realpath_SOURCES = test-realpath.c +test_realpath_LDADD = $(test_libs) +test_realpath_DEPENDENCIES = $(test_deps) + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-sieve/util/test-realpath.c b/src/lib-sieve/util/test-realpath.c new file mode 100644 index 000000000..61cc2ea34 --- /dev/null +++ b/src/lib-sieve/util/test-realpath.c @@ -0,0 +1,283 @@ +/* Copyright (c) 2009-2018 Pigeonhole authors, see the included COPYING file */ + +#include "test-lib.h" +#include "abspath.h" +#include "realpath.h" +#include "unlink-directory.h" +#include "str.h" + +#include +#include +#include +#include + +#define TEMP_DIRNAME ".test-realpath" + +static const char *tmpdir; +static const char *cwd; +static const char *link1; +static const char *link2; +static const char *link3; +static const char *link4; + +static void test_local_path(void) +{ + const char *expected = t_strconcat(cwd, "/README.md", NULL); + const char *npath = NULL; + test_assert(t_normpath_to("README.md", cwd, &npath) == 0); + test_assert_strcmp(npath, expected); +} + +static void test_absolute_path_no_change(void) +{ + const char *npath = NULL; + test_assert(t_normpath_to("/", "/", &npath) == 0); + test_assert_strcmp(npath, "/"); + + test_assert(t_normpath_to(cwd, cwd, &npath) == 0); + test_assert_strcmp(npath, cwd); +} + +static int path_height(const char *p) +{ + int n; + for (n = 0; *p != '\0'; ++p) + n += *p == '/'; + return n; +} + +static void test_travel_to_root(void) +{ + int l = path_height(cwd); + const char *npath = cwd; + for (npath = cwd; l != 0; l--) { + test_assert_idx(t_normpath_to("../", npath, &npath) == 0, l); + } + test_assert_strcmp(npath, "/"); +} + +static void test_extra_slashes(void) +{ + const char *npath = NULL; + test_assert(t_normpath_to(".", cwd, &npath) == 0); + test_assert_strcmp(npath, cwd); + + test_assert(t_normpath_to("./", cwd, &npath) == 0); + test_assert_strcmp(npath, cwd); + + test_assert(t_normpath_to(".////", cwd, &npath) == 0); + test_assert_strcmp(npath, cwd); +} + +static void test_nonexistent_path(void) +{ + const char *npath = NULL; + const char *expected = t_strconcat(cwd, "/nonexistent", NULL); + test_assert(t_normpath_to("nonexistent", cwd, &npath) == 0); + test_assert_strcmp(npath, expected); + test_assert(t_realpath_to("nonexistent", cwd, &npath) == -1); +} + +static void test_relative_dotdot(void) +{ + const char *rel_path = "../"TEMP_DIRNAME; + const char *npath = NULL; + test_assert(t_normpath_to(rel_path, tmpdir, &npath) == 0); + test_assert_strcmp(npath, tmpdir); + + test_assert(t_normpath_to("..", tmpdir, &npath) == 0); + test_assert_strcmp(npath, cwd); + + test_assert(t_normpath_to("../", tmpdir, &npath) == 0); + test_assert_strcmp(npath, cwd); + + test_assert(t_normpath_to("../.", tmpdir, &npath) == 0); + test_assert_strcmp(npath, cwd); +} + +static void test_link1(void) +{ + const char *old_dir, *npath = NULL; + test_assert(t_realpath_to(link1, "/", &npath) == 0); + test_assert_strcmp(npath, tmpdir); + + /* .../link1/link1/child */ + test_assert(t_realpath_to(t_strconcat(link1, "/link1/child", NULL), + "/", &npath) == 0); + test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL)); + + /* relative link1/link1/child */ + if (t_get_current_dir(&old_dir) < 0) + i_fatal("t_get_working_dir() failed: %m"); + if (chdir(tmpdir) < 0) + i_fatal("chdir(%s) failed: %m", tmpdir); + test_assert(t_realpath(t_strconcat("link1", "/link1/child", NULL), + &npath) == 0); + if (chdir(old_dir) < 0) + i_fatal("chdir(%s) failed: %m", old_dir); +} + +static void test_link4(void) +{ + const char *npath = NULL; + + test_assert(t_realpath_to(t_strconcat(link1, "/link4/child", NULL), + "/", &npath) == 0); + test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL)); +} + +static void test_link_loop(void) +{ + const char *npath = NULL; + errno = 0; + test_assert(t_realpath_to(link2, "/", &npath) == -1); + test_assert(errno == ELOOP); +} + +static void test_abspath_vs_normpath(void) +{ + const char *abs = t_abspath_to("../../bin", "/usr/lib/"); + test_assert_strcmp(abs, "/usr/lib//../../bin"); + + const char *norm = NULL; + test_assert(t_normpath_to("../../bin", "/usr///lib/", &norm) == 0); + test_assert_strcmp(norm, "/bin"); +} + +static void create_links(const char *tmpdir) +{ + link1 = t_strconcat(tmpdir, "/link1", NULL); + if (symlink(tmpdir, link1) < 0) + i_fatal("symlink(%s, %s) failed: %m", tmpdir, link1); + + const char *link1_child = t_strconcat(link1, "/child", NULL); + int fd = creat(link1_child, 0600); + if (fd == -1) + i_fatal("creat(%s) failed: %m", link1_child); + i_close_fd(&fd); + + /* link2 and link3 point to each other to create a loop */ + link2 = t_strconcat(tmpdir, "/link2", NULL); + link3 = t_strconcat(tmpdir, "/link3", NULL); + if (symlink(link3, link2) < 0) + i_fatal("symlink(%s, %s) failed: %m", link3, link2); + if (symlink(link2, link3) < 0) + i_fatal("symlink(%s, %s) failed: %m", link2, link3); + + /* link4 points to link1 */ + link4 = t_strconcat(tmpdir, "/link4", NULL); + if (symlink("link1", link4) < 0) + i_fatal("symlink(link1, %s) failed: %m", link4); +} + +static void test_link_alloc(void) +{ +#define COMPONENT_COMPONENT "/component-component" + const char *o_tmpdir; + + /* idea here is to make sure component-component + would optimally hit to the nearest_power value. + + it has to be big enough to cause requirement for + allocation in t_realpath. */ + string_t *basedir = t_str_new(256); + str_append(basedir, cwd); + str_append(basedir, "/"TEMP_DIRNAME); + size_t len = nearest_power(I_MAX(127, str_len(basedir) + strlen(COMPONENT_COMPONENT) + 1)) - + strlen(COMPONENT_COMPONENT); + + while(str_len(basedir) < len) { + str_append(basedir, COMPONENT_COMPONENT); + (void)mkdir(str_c(basedir), 0700); + } + o_tmpdir = tmpdir; + tmpdir = str_c(basedir); + + create_links(tmpdir); + + test_link1(); + test_link_loop(); + + tmpdir = o_tmpdir; +} + +static void test_link_alloc2(void) +{ + const char *o_tmpdir; + + /* try enough different sized base directory lengths so the code + hits the different reallocations and tests for off-by-one errors */ + string_t *basedir = t_str_new(256); + str_append(basedir, cwd); + str_append(basedir, "/"TEMP_DIRNAME); + str_append_c(basedir, '/'); + size_t base_len = str_len(basedir); + + o_tmpdir = tmpdir; + /* path_normalize() initially allocates 128 bytes, so we'll test paths + up to that length+1. */ + unsigned char buf[128+1]; + memset(buf, 'x', sizeof(buf)); + for (size_t i = 1; i <= sizeof(buf); i++) { + str_truncate(basedir, base_len); + str_append_n(basedir, buf, i); + tmpdir = str_c(basedir); + (void)mkdir(str_c(basedir), 0700); + + create_links(tmpdir); + test_link1(); + test_link_loop(); + } + tmpdir = o_tmpdir; +} + +static void test_cleanup(void) +{ + if (unlink_directory(tmpdir, UNLINK_DIRECTORY_FLAG_RMDIR) < 0) + i_error("unlink_directory() failed: %m"); +} + +static void test_init(void) +{ + test_assert(t_get_current_dir(&cwd) == 0); + tmpdir = t_strconcat(cwd, "/"TEMP_DIRNAME, NULL); + + test_cleanup(); + if (mkdir(tmpdir, 0700) < 0) { + i_fatal("mkdir: %m"); + } + + create_links(tmpdir); +} + +static void test_realpath(void) +{ + test_begin("realpath"); + alarm(20); + test_init(); + test_local_path(); + test_absolute_path_no_change(); + test_travel_to_root(); + test_extra_slashes(); + test_nonexistent_path(); + test_relative_dotdot(); + test_link1(); + test_link4(); + test_link_loop(); + test_abspath_vs_normpath(); + test_link_alloc(); + test_link_alloc2(); + test_cleanup(); + alarm(0); + test_end(); +} + +int main(void) +{ + static void (*test_functions[])(void) = { + test_realpath, + NULL + }; + return test_run(test_functions); +} +