Home | History | Annotate | Download | only in in.ftpd
      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 (at) 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 (at) 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 
     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  */
    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