1 /* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "str.h"
5 #include "path-util.h"
6
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10
11 #define PATH_UTIL_MAX_PATH 8*1024
12 #define PATH_UTIL_MAX_SYMLINKS 80
13
t_getcwd_noalloc(char ** dir_r,size_t * asize_r,const char ** error_r)14 static int t_getcwd_noalloc(char **dir_r, size_t *asize_r,
15 const char **error_r) ATTR_NULL(2)
16 {
17 /* @UNSAFE */
18 char *dir;
19 size_t asize = 128;
20
21 dir = t_buffer_get(asize);
22 while (getcwd(dir, asize) == NULL) {
23 if (errno != ERANGE) {
24 *error_r = t_strdup_printf("getcwd() failed: %m");
25 return -1;
26 }
27 asize = nearest_power(asize+1);
28 dir = t_buffer_get(asize);
29 }
30 if (asize_r != NULL)
31 *asize_r = asize;
32 *dir_r = dir;
33 return 0;
34 }
35
path_normalize(const char * path,bool resolve_links,const char ** npath_r,const char ** error_r)36 static int path_normalize(const char *path, bool resolve_links,
37 const char **npath_r, const char **error_r)
38 {
39 /* @UNSAFE */
40 unsigned int link_count = 0;
41 char *npath, *npath_pos;
42 const char *p;
43 size_t asize;
44
45 i_assert(path != NULL);
46 i_assert(npath_r != NULL);
47 i_assert(error_r != NULL);
48
49 if (path[0] != '/') {
50 /* relative; initialize npath with current directory */
51 if (t_getcwd_noalloc(&npath, &asize, error_r) < 0)
52 return -1;
53 npath_pos = npath + strlen(npath);
54 i_assert(npath[0] == '/');
55 } else {
56 /* absolute; initialize npath with root */
57 asize = 128;
58 npath = t_buffer_get(asize);
59 npath[0] = '/';
60 npath_pos = npath + 1;
61 }
62
63 p = path;
64 while (*p != '\0') {
65 struct stat st;
66 ptrdiff_t seglen;
67 const char *segend;
68
69 /* skip duplicate slashes */
70 while (*p == '/')
71 p++;
72
73 /* find end of path segment */
74 for (segend = p; *segend != '\0' && *segend != '/'; segend++);
75
76 if (segend == p)
77 break; /* '\0' */
78 seglen = segend - p;
79 if (seglen == 1 && p[0] == '.') {
80 /* a reference to this segment; nothing to do */
81 } else if (seglen == 2 && p[0] == '.' && p[1] == '.') {
82 /* a reference to parent segment; back up to previous
83 * slash */
84 i_assert(npath_pos >= npath);
85 if ((npath_pos - npath) > 1) {
86 if (*(npath_pos-1) == '/')
87 npath_pos--;
88 for (; *(npath_pos-1) != '/'; npath_pos--);
89 }
90 } else {
91 /* allocate space if necessary */
92 i_assert(npath_pos >= npath);
93 if ((size_t)((npath_pos - npath) + seglen + 1) >= asize) {
94 ptrdiff_t npath_offset = npath_pos - npath;
95 asize = nearest_power(npath_offset + seglen + 2);
96 npath = t_buffer_reget(npath, asize);
97 npath_pos = npath + npath_offset;
98 }
99
100 /* make sure npath now ends in slash */
101 i_assert(npath_pos > npath);
102 if (*(npath_pos-1) != '/') {
103 i_assert((size_t)((npath_pos - npath) + 1) < asize);
104 *(npath_pos++) = '/';
105 }
106
107 /* copy segment to normalized path */
108 i_assert(npath_pos >= npath);
109 i_assert((size_t)((npath_pos - npath) + seglen) < asize);
110 memmove(npath_pos, p, seglen);
111 npath_pos += seglen;
112 }
113
114 if (resolve_links) {
115 /* stat path up to here (segend points to tail) */
116 *npath_pos = '\0';
117 if (lstat(npath, &st) < 0) {
118 *error_r = t_strdup_printf("lstat() failed: %m");
119 return -1;
120 }
121
122 if (S_ISLNK (st.st_mode)) {
123 /* symlink */
124 char *npath_link;
125 size_t lsize = 128, tlen = strlen(segend), espace;
126 size_t ltlen = (link_count == 0 ? 0 : tlen);
127 ssize_t ret;
128
129 /* limit link dereferences */
130 if (++link_count > PATH_UTIL_MAX_SYMLINKS) {
131 errno = ELOOP;
132 *error_r = "Too many symlink dereferences";
133 return -1;
134 }
135
136 /* allocate space for preserving tail of previous symlink and
137 first attempt at reading symlink with room for the tail
138
139 buffer will look like this:
140 [npath][0][preserved tail][link buffer][room for tail][0]
141 */
142 espace = ltlen + tlen + 2;
143 i_assert(npath_pos >= npath);
144 if ((size_t)((npath_pos - npath) + espace + lsize) >= asize) {
145 ptrdiff_t npath_offset = npath_pos - npath;
146 asize = nearest_power((npath_offset + espace + lsize) + 1);
147 lsize = asize - (npath_offset + espace);
148 npath = t_buffer_reget(npath, asize);
149 npath_pos = npath + npath_offset;
150 }
151
152 if (ltlen > 0) {
153 /* preserve tail just after end of npath */
154 i_assert(npath_pos >= npath);
155 i_assert((size_t)((npath_pos + 1 - npath) + ltlen) < asize);
156 memmove(npath_pos + 1, segend, ltlen);
157 }
158
159 /* read the symlink after the preserved tail */
160 for (;;) {
161 npath_link = (npath_pos + 1) + ltlen;
162
163 i_assert(npath_link >= npath_pos);
164 i_assert((size_t)((npath_link - npath) + lsize) < asize);
165
166 /* attempt to read the link */
167 if ((ret=readlink(npath, npath_link, lsize)) < 0) {
168 *error_r = t_strdup_printf("readlink() failed: %m");
169 return -1;
170 }
171 if ((size_t)ret < lsize) {
172 /* POSIX doesn't guarantee the presence of a NIL */
173 npath_link[ret] = '\0';
174 break;
175 }
176
177 /* sum of new symlink content length
178 * and path tail length may not
179 exceed maximum */
180 if ((size_t)(ret + tlen) >= PATH_UTIL_MAX_PATH) {
181 errno = ENAMETOOLONG;
182 *error_r = "Resulting path is too long";
183 return -1;
184 }
185
186 /* try again with bigger buffer,
187 we need to allocate more space as well if lsize == ret,
188 because the returned link may have gotten truncated */
189 espace = ltlen + tlen + 2;
190 i_assert(npath_pos >= npath);
191 if ((size_t)((npath_pos - npath) + espace + lsize) >= asize ||
192 lsize == (size_t)ret) {
193 ptrdiff_t npath_offset = npath_pos - npath;
194 asize = nearest_power((npath_offset + espace + lsize) + 1);
195 lsize = asize - (npath_offset + espace);
196 npath = t_buffer_reget(npath, asize);
197 npath_pos = npath + npath_offset;
198 }
199 }
200
201 /* add tail of previous path at end of symlink */
202 i_assert(npath_link >= npath);
203 if (ltlen > 0) {
204 i_assert(npath_pos >= npath);
205 i_assert((size_t)((npath_pos - npath) + 1 + tlen) < asize);
206 i_assert((size_t)((npath_link - npath) + ret + tlen) < asize);
207 memcpy(npath_link + ret, npath_pos + 1, tlen);
208 } else {
209 i_assert((size_t)((npath_link - npath) + ret + tlen) < asize);
210 memcpy(npath_link + ret, segend, tlen);
211 }
212 *(npath_link+ret+tlen) = '\0';
213
214 /* use as new source path */
215 path = segend = npath_link;
216
217 if (path[0] == '/') {
218 /* absolute symlink; start over at root */
219 npath_pos = npath + 1;
220 } else {
221 /* relative symlink; back up to previous segment */
222 i_assert(npath_pos >= npath);
223 if ((npath_pos - npath) > 1) {
224 if (*(npath_pos-1) == '/')
225 npath_pos--;
226 for (; *(npath_pos-1) != '/'; npath_pos--);
227 }
228 }
229
230 } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) {
231 /* not last segment, but not a directory either */
232 errno = ENOTDIR;
233 *error_r = t_strdup_printf("Not a directory: %s", npath);
234 return -1;
235 }
236 }
237
238 p = segend;
239 }
240
241 i_assert(npath_pos >= npath);
242 i_assert((size_t)(npath_pos - npath) < asize);
243
244 /* remove any trailing slash */
245 if ((npath_pos - npath) > 1 && *(npath_pos-1) == '/')
246 npath_pos--;
247 *npath_pos = '\0';
248
249 t_buffer_alloc(npath_pos - npath + 1);
250 *npath_r = npath;
251 return 0;
252 }
253
t_normpath(const char * path,const char ** npath_r,const char ** error_r)254 int t_normpath(const char *path, const char **npath_r, const char **error_r)
255 {
256 return path_normalize(path, FALSE, npath_r, error_r);
257 }
258
t_normpath_to(const char * path,const char * root,const char ** npath_r,const char ** error_r)259 int t_normpath_to(const char *path, const char *root, const char **npath_r,
260 const char **error_r)
261 {
262 i_assert(path != NULL);
263 i_assert(root != NULL);
264 i_assert(npath_r != NULL);
265
266 if (*path == '/')
267 return t_normpath(path, npath_r, error_r);
268
269 return t_normpath(t_strconcat(root, "/", path, NULL), npath_r, error_r);
270 }
271
t_realpath(const char * path,const char ** npath_r,const char ** error_r)272 int t_realpath(const char *path, const char **npath_r, const char **error_r)
273 {
274 return path_normalize(path, TRUE, npath_r, error_r);
275 }
276
t_realpath_to(const char * path,const char * root,const char ** npath_r,const char ** error_r)277 int t_realpath_to(const char *path, const char *root, const char **npath_r,
278 const char **error_r)
279 {
280 i_assert(path != NULL);
281 i_assert(root != NULL);
282 i_assert(npath_r != NULL);
283
284 if (*path == '/')
285 return t_realpath(path, npath_r, error_r);
286
287 return t_realpath(t_strconcat(root, "/", path, NULL), npath_r, error_r);
288 }
289
t_abspath(const char * path,const char ** abspath_r,const char ** error_r)290 int t_abspath(const char *path, const char **abspath_r, const char **error_r)
291 {
292 i_assert(path != NULL);
293 i_assert(abspath_r != NULL);
294 i_assert(error_r != NULL);
295
296 if (*path == '/') {
297 *abspath_r = path;
298 return 0;
299 }
300
301 const char *dir, *error;
302 if (t_get_working_dir(&dir, &error) < 0) {
303 *error_r = t_strconcat("Failed to get working directory: ",
304 error, NULL);
305 return -1;
306 }
307 *abspath_r = t_strconcat(dir, "/", path, NULL);
308 return 0;
309 }
310
t_abspath_to(const char * path,const char * root)311 const char *t_abspath_to(const char *path, const char *root)
312 {
313 i_assert(path != NULL);
314 i_assert(root != NULL);
315
316 if (*path == '/')
317 return path;
318
319 return t_strconcat(root, "/", path, NULL);
320 }
321
t_get_working_dir(const char ** dir_r,const char ** error_r)322 int t_get_working_dir(const char **dir_r, const char **error_r)
323 {
324 char *dir;
325
326 i_assert(dir_r != NULL);
327 i_assert(error_r != NULL);
328 if (t_getcwd_noalloc(&dir, NULL, error_r) < 0)
329 return -1;
330
331 t_buffer_alloc(strlen(dir) + 1);
332 *dir_r = dir;
333 return 0;
334 }
335
t_readlink(const char * path,const char ** dest_r,const char ** error_r)336 int t_readlink(const char *path, const char **dest_r, const char **error_r)
337 {
338 i_assert(error_r != NULL);
339
340 /* @UNSAFE */
341 ssize_t ret;
342 char *dest;
343 size_t size = 128;
344
345 dest = t_buffer_get(size);
346 while ((ret = readlink(path, dest, size)) >= (ssize_t)size) {
347 size = nearest_power(size+1);
348 dest = t_buffer_get(size);
349 }
350 if (ret < 0) {
351 *error_r = t_strdup_printf("readlink() failed: %m");
352 return -1;
353 }
354
355 dest[ret] = '\0';
356 t_buffer_alloc(ret + 1);
357 *dest_r = dest;
358 return 0;
359 }
360
t_binary_abspath(const char ** binpath,const char ** error_r)361 bool t_binary_abspath(const char **binpath, const char **error_r)
362 {
363 const char *path_env, *const *paths;
364 string_t *path;
365
366 if (**binpath == '/') {
367 /* already have absolute path */
368 return TRUE;
369 } else if (strchr(*binpath, '/') != NULL) {
370 /* relative to current directory */
371 const char *error;
372 if (t_abspath(*binpath, binpath, &error) < 0) {
373 *error_r = t_strdup_printf("t_abspath(%s) failed: %s",
374 *binpath, error);
375 return FALSE;
376 }
377 return TRUE;
378 } else if ((path_env = getenv("PATH")) != NULL) {
379 /* we have to find our executable from path */
380 path = t_str_new(256);
381 paths = t_strsplit(path_env, ":");
382 for (; *paths != NULL; paths++) {
383 str_append(path, *paths);
384 str_append_c(path, '/');
385 str_append(path, *binpath);
386 if (access(str_c(path), X_OK) == 0) {
387 *binpath = str_c(path);
388 return TRUE;
389 }
390 str_truncate(path, 0);
391 }
392 *error_r = "Could not find the wanted executable from PATH";
393 return FALSE;
394 } else {
395 *error_r = "PATH environment variable undefined";
396 return FALSE;
397 }
398 }
399