/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "path-util.h" #include #include #include #define PATH_UTIL_MAX_PATH 8*1024 #define PATH_UTIL_MAX_SYMLINKS 80 static int t_getcwd_noalloc(char **dir_r, size_t *asize_r, const char **error_r) ATTR_NULL(2) { /* @UNSAFE */ char *dir; size_t asize = 128; dir = t_buffer_get(asize); while (getcwd(dir, asize) == NULL) { if (errno != ERANGE) { *error_r = t_strdup_printf("getcwd() failed: %m"); return -1; } asize = nearest_power(asize+1); dir = t_buffer_get(asize); } if (asize_r != NULL) *asize_r = asize; *dir_r = dir; return 0; } static int path_normalize(const char *path, bool resolve_links, const char **npath_r, const char **error_r) { /* @UNSAFE */ unsigned int link_count = 0; char *npath, *npath_pos; const char *p; size_t asize; i_assert(path != NULL); i_assert(npath_r != NULL); i_assert(error_r != NULL); if (path[0] != '/') { /* relative; initialize npath with current directory */ if (t_getcwd_noalloc(&npath, &asize, error_r) < 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 slashes */ 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 */ i_assert(npath_pos >= npath); if ((npath_pos - npath) > 1) { if (*(npath_pos-1) == '/') npath_pos--; for (; *(npath_pos-1) != '/'; npath_pos--); } } else { /* allocate space if necessary */ i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + seglen + 1) >= 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; } /* make sure npath now ends in slash */ i_assert(npath_pos > npath); if (*(npath_pos-1) != '/') { i_assert((size_t)((npath_pos - npath) + 1) < asize); *(npath_pos++) = '/'; } /* copy segment to normalized path */ i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos - npath) + seglen) < asize); memmove(npath_pos, p, seglen); npath_pos += seglen; } if (resolve_links) { /* stat path up to here (segend points to tail) */ *npath_pos = '\0'; if (lstat(npath, &st) < 0) { *error_r = t_strdup_printf("lstat() failed: %m"); 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 > PATH_UTIL_MAX_SYMLINKS) { errno = ELOOP; *error_r = "Too many symlink dereferences"; 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; i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + espace + lsize) >= 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 */ i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos + 1 - npath) + ltlen) < asize); memmove(npath_pos + 1, segend, ltlen); } /* read the symlink after the preserved tail */ for (;;) { npath_link = (npath_pos + 1) + ltlen; i_assert(npath_link >= npath_pos); i_assert((size_t)((npath_link - npath) + lsize) < asize); /* attempt to read the link */ if ((ret=readlink(npath, npath_link, lsize)) < 0) { *error_r = t_strdup_printf("readlink() failed: %m"); return -1; } if ((size_t)ret < lsize) { /* POSIX doesn't guarantee the presence of a NIL */ npath_link[ret] = '\0'; break; } /* sum of new symlink content length * and path tail length may not exceed maximum */ if ((size_t)(ret + tlen) >= PATH_UTIL_MAX_PATH) { errno = ENAMETOOLONG; *error_r = "Resulting path is too long"; return -1; } /* try again with bigger buffer, we need to allocate more space as well if lsize == ret, because the returned link may have gotten truncated */ espace = ltlen + tlen + 2; i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + espace + lsize) >= asize || lsize == (size_t)ret) { 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 */ i_assert(npath_link >= npath); if (ltlen > 0) { i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos - npath) + 1 + tlen) < asize); i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); memcpy(npath_link + ret, npath_pos + 1, tlen); } else { i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); 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 */ i_assert(npath_pos >= npath); if ((npath_pos - npath) > 1) { if (*(npath_pos-1) == '/') npath_pos--; for (; *(npath_pos-1) != '/'; npath_pos--); } } } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) { /* not last segment, but not a directory either */ errno = ENOTDIR; *error_r = t_strdup_printf("Not a directory: %s", npath); return -1; } } p = segend; } i_assert(npath_pos >= npath); i_assert((size_t)(npath_pos - npath) < asize); /* 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, const char **error_r) { return path_normalize(path, FALSE, npath_r, error_r); } int t_normpath_to(const char *path, const char *root, const char **npath_r, const char **error_r) { i_assert(path != NULL); i_assert(root != NULL); i_assert(npath_r != NULL); if (*path == '/') return t_normpath(path, npath_r, error_r); return t_normpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); } int t_realpath(const char *path, const char **npath_r, const char **error_r) { return path_normalize(path, TRUE, npath_r, error_r); } int t_realpath_to(const char *path, const char *root, const char **npath_r, const char **error_r) { i_assert(path != NULL); i_assert(root != NULL); i_assert(npath_r != NULL); if (*path == '/') return t_realpath(path, npath_r, error_r); return t_realpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); } int t_abspath(const char *path, const char **abspath_r, const char **error_r) { i_assert(path != NULL); i_assert(abspath_r != NULL); i_assert(error_r != NULL); if (*path == '/') { *abspath_r = path; return 0; } const char *dir, *error; if (t_get_working_dir(&dir, &error) < 0) { *error_r = t_strconcat("Failed to get working directory: ", error, NULL); return -1; } *abspath_r = t_strconcat(dir, "/", path, NULL); return 0; } const char *t_abspath_to(const char *path, const char *root) { i_assert(path != NULL); i_assert(root != NULL); if (*path == '/') return path; return t_strconcat(root, "/", path, NULL); } int t_get_working_dir(const char **dir_r, const char **error_r) { char *dir; i_assert(dir_r != NULL); i_assert(error_r != NULL); if (t_getcwd_noalloc(&dir, NULL, error_r) < 0) return -1; t_buffer_alloc(strlen(dir) + 1); *dir_r = dir; return 0; } int t_readlink(const char *path, const char **dest_r, const char **error_r) { i_assert(error_r != NULL); /* @UNSAFE */ ssize_t ret; char *dest; size_t size = 128; dest = t_buffer_get(size); while ((ret = readlink(path, dest, size)) >= (ssize_t)size) { size = nearest_power(size+1); dest = t_buffer_get(size); } if (ret < 0) { *error_r = t_strdup_printf("readlink() failed: %m"); return -1; } dest[ret] = '\0'; t_buffer_alloc(ret + 1); *dest_r = dest; return 0; } bool t_binary_abspath(const char **binpath, const char **error_r) { const char *path_env, *const *paths; string_t *path; if (**binpath == '/') { /* already have absolute path */ return TRUE; } else if (strchr(*binpath, '/') != NULL) { /* relative to current directory */ const char *error; if (t_abspath(*binpath, binpath, &error) < 0) { *error_r = t_strdup_printf("t_abspath(%s) failed: %s", *binpath, error); return FALSE; } return TRUE; } else if ((path_env = getenv("PATH")) != NULL) { /* we have to find our executable from path */ path = t_str_new(256); paths = t_strsplit(path_env, ":"); for (; *paths != NULL; paths++) { str_append(path, *paths); str_append_c(path, '/'); str_append(path, *binpath); if (access(str_c(path), X_OK) == 0) { *binpath = str_c(path); return TRUE; } str_truncate(path, 0); } *error_r = "Could not find the wanted executable from PATH"; return FALSE; } else { *error_r = "PATH environment variable undefined"; return FALSE; } }