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