diff --git a/src/lib-sieve/util/Makefile.am b/src/lib-sieve/util/Makefile.am index a5648bc32ecdffd5d80ef6d20cab4ba73760383f..40f15f717c0d7eb5983d852684d4d5feae6cdd6f 100644 --- a/src/lib-sieve/util/Makefile.am +++ b/src/lib-sieve/util/Makefile.am @@ -12,13 +12,15 @@ libsieve_util_la_SOURCES = \ rfc2822.c \ program-client-local.c \ program-client-remote.c \ - program-client.c + program-client.c \ + realpath.c headers = \ edit-mail.h \ rfc2822.h \ program-client-private.h \ - program-client.h + program-client.h \ + realpath.h pkginc_libdir=$(dovecot_pkgincludedir)/sieve pkginc_lib_HEADERS = $(headers) diff --git a/src/lib-sieve/util/realpath.c b/src/lib-sieve/util/realpath.c new file mode 100644 index 0000000000000000000000000000000000000000..a16556c2792bebba6b96e4f78c8be92d7984976c --- /dev/null +++ b/src/lib-sieve/util/realpath.c @@ -0,0 +1,237 @@ +/* Copyright (c) 2009-2015 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" + +#include "realpath.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +// FIXME: move/merge to Dovecot + +#define REALPATH_MAX_PATH 8*1024 +#define REALPATH_MAX_SYMLINKS 80 + +static int t_getcwd_alloc(char **dir_r, size_t *asize_r) +{ + /* @UNSAFE */ + char *dir; + size_t asize = 128; + + dir = t_buffer_get(asize); + while (getcwd(dir, asize) == NULL) { + if (errno != ERANGE) + return -1; + asize = nearest_power(asize+1); + dir = t_buffer_get(asize); + } + *asize_r = asize; + *dir_r = dir; + return 0; +} + +static int path_normalize(const char *path, bool resolve_links, + const char **npath_r) +{ + /* @UNSAFE */ + unsigned int link_count = 0; + char *npath, *npath_pos; + const char *p; + size_t asize; + + if (path[0] != '/') { + /* relative; initialize npath with current directory */ + if (t_getcwd_alloc(&npath, &asize) < 0) + return -1; + npath_pos = npath + strlen(npath); + i_assert(npath[0] == '/'); + } else { + /* absolute; initialize npath with root */ + asize = 128; + npath = t_buffer_get(asize); + npath[0] = '/'; + npath_pos = npath + 1; + } + + p = path; + while (*p != '\0') { + struct stat st; + ptrdiff_t seglen; + const char *segend; + + /* skip duplicate shashes */ + while (*p == '/') + p++; + + /* find end of path segment */ + for (segend = p; *segend != '\0' && *segend != '/'; segend++); + + if (segend == p) + break; /* '\0' */ + seglen = segend - p; + if (seglen == 1 && p[0] == '.') { + /* a reference to this segment; nothing to do */ + } else if (seglen == 2 && p[0] == '.' && p[1] == '.') { + /* a reference to parent segment; back up to previous slash */ + if (npath_pos > npath + 1) { + if (*npath_pos == '/') + npath_pos--; + for (; *npath_pos != '/'; npath_pos--); + } + } else { + /* make sure npath now ends in slash */ + if (*(npath_pos-1) != '/') + *(npath_pos++) = '/'; + + /* allocate space if necessary */ + if ((npath_pos + seglen + 1) >= (npath + asize)) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power(npath_offset + seglen + 2); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + + /* copy segment to normalized path */ + (void)memmove(npath_pos, p, segend - p); + npath_pos += seglen; + } + + if (resolve_links) { + /* stat path up to here (segend points to tail) */ + *npath_pos = '\0'; + if (lstat(npath, &st) < 0) + return -1; + + if (S_ISLNK (st.st_mode)) { + /* symlink */ + char *npath_link; + size_t lsize = 128, tlen = strlen(segend), espace; + size_t ltlen = (link_count == 0 ? 0 : tlen); + ssize_t ret; + + /* limit link dereferences */ + if (++link_count > REALPATH_MAX_SYMLINKS) { + errno = ELOOP; + return -1; + } + + /* allocate space for preserving tail of previous symlink and + first attempt at reading symlink with room for the tail + + buffer will look like this: + [npath][0][preserved tail][link buffer][room for tail][0] + */ + espace = ltlen + tlen + 2; + if ((npath_pos + espace + lsize) >= (npath + asize)) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power((npath_offset + espace + lsize) + 1); + lsize = asize - (npath_offset + espace); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + + if (ltlen > 0) { + /* preserve tail just after end of npath */ + (void)memmove(npath_pos + 1, segend, ltlen); + } + + /* read the symlink after the preserved tail */ + for (;;) { + npath_link = (npath_pos + 1) + ltlen; + + /* attempt to read the link */ + if ((ret=readlink(npath, npath_link, lsize)) < 0) + return -1; + if ((size_t)ret < lsize) + break; + + /* sum of new symlink content length and path tail length may not + exeed maximum */ + if ((size_t)(ret + tlen) >= REALPATH_MAX_PATH) { + errno = ENAMETOOLONG; + return -1; + } + + /* try again with bigger buffer */ + espace = ltlen + tlen + 2; + if ((npath_pos + espace + lsize) >= (npath + asize)) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power((npath_offset + espace + lsize) + 1); + lsize = asize - (npath_offset + espace); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + } + + /* add tail of previous path at end of symlink */ + if (ltlen > 0) + (void)memcpy(npath_link + ret, npath_pos + 1, tlen); + else + (void)memcpy(npath_link + ret, segend, tlen); + *(npath_link+ret+tlen) = '\0'; + + /* use as new source path */ + path = segend = npath_link; + + if (path[0] == '/') { + /* absolute symlink; start over at root */ + npath_pos = npath + 1; + } else { + /* relative symlink; back up to previous segment */ + if (npath_pos > npath + 1) { + if (*npath_pos == '/') + npath_pos--; + for (; *npath_pos != '/'; npath_pos--); + } + } + + } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) { + /* not last segment, but not a directory either */ + errno = ENOTDIR; + return -1; + } + } + + p = segend; + } + + /* remove any trailing slash */ + if (npath_pos > npath + 1 && *(npath_pos-1) == '/') + npath_pos--; + *npath_pos = '\0'; + + t_buffer_alloc(npath_pos - npath + 1); + *npath_r = npath; + return 0; +} + +int t_normpath(const char *path, const char **npath_r) +{ + return path_normalize(path, FALSE, npath_r); +} + +int t_normpath_to(const char *path, const char *root, + const char **npath_r) +{ + if (*path == '/') + return t_normpath(path, npath_r); + + return t_normpath(t_strconcat(root, "/", path, NULL), npath_r); +} + +int t_realpath(const char *path, const char **npath_r) +{ + return path_normalize(path, TRUE, npath_r); +} + +int t_realpath_to(const char *path, const char *root, + const char **npath_r) +{ + if (*path == '/') + return t_realpath(path, npath_r); + + return t_realpath(t_strconcat(root, "/", path, NULL), npath_r); +} diff --git a/src/lib-sieve/util/realpath.h b/src/lib-sieve/util/realpath.h new file mode 100644 index 0000000000000000000000000000000000000000..4a5d34288fcc23f93cb449ddbc4d1f7f36265bdd --- /dev/null +++ b/src/lib-sieve/util/realpath.h @@ -0,0 +1,33 @@ +#ifndef REALPATH_H +#define REALPATH_H + +/* Returns path as the normalized absolute path, which means that './' + and '../' components are resolved, and that duplicate and trailing + slashes are removed. If it's not already the absolute path, it's + assumed to be relative to the current working directory. + + NOTE: Be careful with this function. The resolution of '../' components + with the parent component as if it were a normal directory is not valid + if the path contains symbolic links. + */ +int t_normpath(const char *path, const char **npath_r); +/* Like t_normpath(), but path is relative to given root. */ +int t_normpath_to(const char *path, const char *root, + const char **npath_r); + +/* Returns path as the real normalized absolute path, which means that all + symbolic links in the path are resolved, that './' and '../' components + are resolved, and that duplicate and trailing slashes are removed. If it's + not already the absolute path, it's assumed to be relative to the current + working directory. + + NOTE: This function calls stat() for each path component and more when + there are symbolic links (just like POSIX realpath()). + */ +int t_realpath(const char *path, const char **npath_r); +/* Like t_realpath(), but path is relative to given root. */ +int t_realpath_to(const char *path, const char *root, + const char **npath_r); + +#endif +