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