1 /*
2 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5
6 #pragma ident "%Z%%M% %I% %E% SMI"
7
8 /****************************************************************************
9 Copyright (c) 1999,2000 WU-FTPD Development Group.
10 All rights reserved.
11
12 Portions Copyright (c) 1980, 1985, 1988, 1989, 1990, 1991, 1993, 1994
13 The Regents of the University of California.
14 Portions Copyright (c) 1993, 1994 Washington University in Saint Louis.
15 Portions Copyright (c) 1996, 1998 Berkeley Software Design, Inc.
16 Portions Copyright (c) 1989 Massachusetts Institute of Technology.
17 Portions Copyright (c) 1998 Sendmail, Inc.
18 Portions Copyright (c) 1983, 1995, 1996, 1997 Eric P. Allman.
19 Portions Copyright (c) 1997 by Stan Barber.
20 Portions Copyright (c) 1997 by Kent Landfield.
21 Portions Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997
22 Free Software Foundation, Inc.
23
24 Use and distribution of this software and its source code are governed
25 by the terms and conditions of the WU-FTPD Software License ("LICENSE").
26
27 If you did not receive a copy of the license, it may be obtained online
28 at http://www.wu-ftpd.org/license.html.
29
30 $Id: realpath.c,v 1.11 2000/07/01 18:17:39 wuftpd Exp $
31
32 ****************************************************************************/
33 /* Originally taken from FreeBSD 3.0's libc; adapted to handle chroot
34 * directories in BeroFTPD by Bernhard Rosenkraenzer
35 * <bero@beroftpd.unix.eu.org>
36 *
37 * Added super-user permissions so we can determine the real pathname even
38 * if the user cannot access the file. <lundberg+wuftpd@vr.net>
39 */
40 #include "config.h"
41
42 #include <sys/param.h>
43 #include <sys/stat.h>
44
45 #include <errno.h>
46 #if defined(HAVE_FCNTL_H)
47 #include <fcntl.h>
48 #endif
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include "proto.h"
53
54 #ifndef MAXSYMLINKS /* Workaround for Linux libc 4.x/5.x */
55 #define MAXSYMLINKS 5
56 #endif
57
58 #ifndef HAVE_LSTAT
59 #define lstat stat
60 #endif
61
wu_realpath(const char * path,char resolved_path[MAXPATHLEN],char * chroot_path)62 char *wu_realpath(const char *path, char resolved_path[MAXPATHLEN], char *chroot_path)
63 {
64 char *ptr;
65 char q[MAXPATHLEN];
66
67 fb_realpath(path, q);
68
69 if (chroot_path == NULL)
70 strcpy(resolved_path, q);
71 else {
72 strcpy(resolved_path, chroot_path);
73 if (q[0] != '/') {
74 if (strlen(resolved_path) + strlen(q) < MAXPATHLEN)
75 strcat(resolved_path, q);
76 else /* Avoid buffer overruns... */
77 return NULL;
78 }
79 else if (q[1] != '\0') {
80 for (ptr = q; *ptr != '\0'; ptr++);
81 if (ptr == resolved_path || *--ptr != '/') {
82 if (strlen(resolved_path) + strlen(q) < MAXPATHLEN)
83 strcat(resolved_path, q);
84 else /* Avoid buffer overruns... */
85 return NULL;
86 }
87 else {
88 if (strlen(resolved_path) + strlen(q) - 1 < MAXPATHLEN)
89 strcat(resolved_path, &q[1]);
90 else /* Avoid buffer overruns... */
91 return NULL;
92 }
93 }
94 }
95 return resolved_path;
96 }
97
98 /*
99 * char *fb_realpath(const char *path, char resolved_path[MAXPATHLEN]);
100 *
101 * Find the real name of path, by removing all ".", ".." and symlink
102 * components. Returns (resolved) on success, or (NULL) on failure,
103 * in which case the path which caused trouble is left in (resolved).
104 */
fb_realpath(const char * path,char * resolved)105 char *fb_realpath(const char *path, char *resolved)
106 {
107 struct stat sb;
108 int fd, n, rootd, serrno;
109 char *p, *q, wbuf[MAXPATHLEN];
110 int symlinks = 0;
111 int resultcode;
112 #ifdef HAS_NO_FCHDIR
113 /* AIX Has no fchdir() so we hope the getcwd() call doesn't overrun the buffer! */
114 char cwd[MAXPATHLEN + 1];
115 char *pcwd;
116 #endif
117
118 /* Save the starting point. */
119 errno = 0;
120 #ifdef HAS_NO_FCHDIR
121 #ifdef HAVE_GETCWD
122 pcwd = getcwd(cwd, sizeof(cwd));
123 #else
124 pcwd = getwd(cwd);
125 #endif
126 #else
127 fd = open(".", O_RDONLY);
128 #endif
129 if (EACCES == errno) {
130 uid_t userid = geteuid();
131 access_priv_on(0);
132 #ifdef HAS_NO_FCHDIR
133 #ifdef HAVE_GETCWD
134 pcwd = getcwd(cwd, sizeof(cwd));
135 #else
136 pcwd = getwd(cwd);
137 #endif
138 #else
139 fd = open(".", O_RDONLY);
140 #endif
141 access_priv_off(userid);
142 }
143 #ifdef HAS_NO_FCHDIR
144 if (pcwd == NULL)
145 #else
146 if (fd < 0)
147 #endif
148 {
149 (void) strcpy(resolved, ".");
150 return (NULL);
151 }
152
153 /*
154 * Find the dirname and basename from the path to be resolved.
155 * Change directory to the dirname component.
156 * lstat the basename part.
157 * if it is a symlink, read in the value and loop.
158 * if it is a directory, then change to that directory.
159 * get the current directory name and append the basename.
160 */
161 (void) strncpy(resolved, path, MAXPATHLEN - 1);
162 resolved[MAXPATHLEN - 1] = '\0';
163 loop:
164 q = strrchr(resolved, '/');
165 if (q != NULL) {
166 p = q + 1;
167 if (q == resolved)
168 q = "/";
169 else {
170 do {
171 --q;
172 } while (q > resolved && *q == '/');
173 q[1] = '\0';
174 q = resolved;
175 }
176 errno = 0;
177 resultcode = chdir(q);
178 if (EACCES == errno) {
179 uid_t userid = geteuid();
180 access_priv_on(0);
181 errno = 0;
182 resultcode = chdir(q);
183 access_priv_off(userid);
184 }
185 if (resultcode < 0)
186 goto err1;
187 }
188 else
189 p = resolved;
190
191 /* Deal with the last component. */
192 if (*p != '\0') {
193 errno = 0;
194 resultcode = lstat(p, &sb);
195 if (EACCES == errno) {
196 uid_t userid = geteuid();
197 access_priv_on(0);
198 errno = 0;
199 resultcode = lstat(p, &sb);
200 access_priv_off(userid);
201 }
202 if (resultcode == 0) {
203 #ifdef HAVE_LSTAT
204 if (S_ISLNK(sb.st_mode)) {
205 if (++symlinks > MAXSYMLINKS) {
206 errno = ELOOP;
207 goto err1;
208 }
209 errno = 0;
210 {
211 size_t len = strlen(p);
212 char *tmp = calloc(len + 1, sizeof(char));
213 if (tmp == 0) {
214 serrno = errno;
215 goto err1;
216 }
217 strcpy(tmp, p);
218 p = tmp;
219 }
220 n = readlink(p, resolved, MAXPATHLEN);
221 if (EACCES == errno) {
222 uid_t userid = geteuid();
223 access_priv_on(0);
224 errno = 0;
225 n = readlink(p, resolved, MAXPATHLEN);
226 access_priv_off(userid);
227 }
228 if (n < 0) {
229 free(p);
230 goto err1;
231 }
232 free(p);
233 /* n should be less than MAXPATHLEN, but check to be safe */
234 if (n >= MAXPATHLEN)
235 n = MAXPATHLEN - 1;
236 resolved[n] = '\0';
237 goto loop;
238 }
239 #endif /* HAVE_LSTAT */
240 if (S_ISDIR(sb.st_mode)) {
241 errno = 0;
242 resultcode = chdir(p);
243 if (EACCES == errno) {
244 uid_t userid = geteuid();
245 access_priv_on(0);
246 errno = 0;
247 resultcode = chdir(p);
248 access_priv_off(userid);
249 }
250 if (resultcode < 0)
251 goto err1;
252 p = "";
253 }
254 }
255 }
256
257 /*
258 * Save the last component name and get the full pathname of
259 * the current directory.
260 */
261 (void) strcpy(wbuf, p);
262 errno = 0;
263 #ifdef HAVE_GETCWD
264 resultcode = getcwd(resolved, MAXPATHLEN) == NULL ? 0 : 1;
265 #else
266 resultcode = getwd(resolved) == NULL ? 0 : 1;
267 if (resolved[MAXPATHLEN - 1] != '\0') {
268 resultcode = 0;
269 errno = ERANGE;
270 }
271 #endif
272 if (EACCES == errno) {
273 uid_t userid = geteuid();
274 access_priv_on(0);
275 errno = 0;
276 #ifdef HAVE_GETCWD
277 resultcode = getcwd(resolved, MAXPATHLEN) == NULL ? 0 : 1;
278 #else
279 resultcode = getwd(resolved) == NULL ? 0 : 1;
280 if (resolved[MAXPATHLEN - 1] != '\0') {
281 resultcode = 0;
282 errno = ERANGE;
283 }
284 #endif
285 access_priv_off(userid);
286 }
287 if (resultcode == 0)
288 goto err1;
289
290 /*
291 * Join the two strings together, ensuring that the right thing
292 * happens if the last component is empty, or the dirname is root.
293 */
294 if (resolved[0] == '/' && resolved[1] == '\0')
295 rootd = 1;
296 else
297 rootd = 0;
298
299 if (*wbuf) {
300 if (strlen(resolved) + strlen(wbuf) + !rootd + 1 > MAXPATHLEN) {
301 errno = ENAMETOOLONG;
302 goto err1;
303 }
304 if (rootd == 0)
305 (void) strcat(resolved, "/");
306 (void) strcat(resolved, wbuf);
307 }
308
309 /* Go back to where we came from. */
310 errno = 0;
311 #ifdef HAS_NO_FCHDIR
312 resultcode = chdir(cwd);
313 #else
314 resultcode = fchdir(fd);
315 #endif
316 if (EACCES == errno) {
317 uid_t userid = geteuid();
318 access_priv_on(0);
319 errno = 0;
320 #ifdef HAS_NO_FCHDIR
321 resultcode = chdir(cwd);
322 #else
323 resultcode = fchdir(fd);
324 #endif
325 access_priv_off(userid);
326 }
327 if (resultcode < 0) {
328 serrno = errno;
329 goto err2;
330 }
331
332 #ifndef HAS_NO_FCHDIR
333 /* It's okay if the close fails, what's an fd more or less? */
334 (void) close(fd);
335 #endif
336 return (resolved);
337
338 err1:serrno = errno;
339 #ifdef HAS_NO_FCHDIR
340 (void) chdir(cwd);
341 #else
342 (void) fchdir(fd);
343 #endif
344 if (EACCES == errno) {
345 uid_t userid = geteuid();
346 access_priv_on(0);
347 #ifdef HAS_NO_FCHDIR
348 (void) chdir(cwd);
349 #else
350 (void) fchdir(fd);
351 #endif
352 access_priv_off(userid);
353 }
354 #ifdef HAS_NO_FCHDIR
355 err2:errno = serrno;
356 #else
357 err2:(void) close(fd);
358 errno = serrno;
359 #endif
360 return (NULL);
361 }
362