xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.sbin/in.ftpd/realpath.c (revision 0:68f95e015346)
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