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 
  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 }