241 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* eaccess.c - eaccess replacement for the shell, plus other access functions. */
 | 
						|
 | 
						|
/* Copyright (C) 2006-2010 Free Software Foundation, Inc.
 | 
						|
 | 
						|
   This file is part of GNU Bash, the Bourne Again SHell.
 | 
						|
 | 
						|
   Bash is free software: you can redistribute it and/or modify
 | 
						|
   it under the terms of the GNU General Public License as published by
 | 
						|
   the Free Software Foundation, either version 3 of the License, or
 | 
						|
   (at your option) any later version.
 | 
						|
 | 
						|
   Bash is distributed in the hope that it will be useful,
 | 
						|
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
   GNU General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License
 | 
						|
   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
*/
 | 
						|
 | 
						|
#if defined (HAVE_CONFIG_H)
 | 
						|
#  include <config.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
 | 
						|
#include "bashtypes.h"
 | 
						|
 | 
						|
#if defined (HAVE_UNISTD_H)
 | 
						|
#  include <unistd.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include "bashansi.h"
 | 
						|
 | 
						|
#include <errno.h>
 | 
						|
#if !defined (errno)
 | 
						|
extern int errno;
 | 
						|
#endif /* !errno */
 | 
						|
 | 
						|
#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H)
 | 
						|
#  include <sys/file.h>
 | 
						|
#endif /* !_POSIX_VERSION */
 | 
						|
#include "posixstat.h"
 | 
						|
#include "filecntl.h"
 | 
						|
 | 
						|
#include "shell.h"
 | 
						|
 | 
						|
#if !defined (R_OK)
 | 
						|
#define R_OK 4
 | 
						|
#define W_OK 2
 | 
						|
#define X_OK 1
 | 
						|
#define F_OK 0
 | 
						|
#endif /* R_OK */
 | 
						|
 | 
						|
static int path_is_devfd __P((const char *));
 | 
						|
static int sh_stataccess __P((char *, int));
 | 
						|
#if HAVE_DECL_SETREGID
 | 
						|
static int sh_euidaccess __P((char *, int));
 | 
						|
#endif
 | 
						|
 | 
						|
static int
 | 
						|
path_is_devfd (path)
 | 
						|
     const char *path;
 | 
						|
{
 | 
						|
  if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0)
 | 
						|
    return 1;
 | 
						|
  else if (STREQN (path, "/dev/std", 8))
 | 
						|
    {
 | 
						|
      if (STREQ (path+8, "in") || STREQ (path+8, "out") || STREQ (path+8, "err"))
 | 
						|
	return 1;
 | 
						|
      else
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* A wrapper for stat () which disallows pathnames that are empty strings
 | 
						|
   and handles /dev/fd emulation on systems that don't have it. */
 | 
						|
int
 | 
						|
sh_stat (path, finfo)
 | 
						|
     const char *path;
 | 
						|
     struct stat *finfo;
 | 
						|
{
 | 
						|
  static char *pbuf = 0;
 | 
						|
 | 
						|
  if (*path == '\0')
 | 
						|
    {
 | 
						|
      errno = ENOENT;
 | 
						|
      return (-1);
 | 
						|
    }
 | 
						|
  if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0)
 | 
						|
    {
 | 
						|
#if !defined (HAVE_DEV_FD)
 | 
						|
      intmax_t fd;
 | 
						|
      int r;
 | 
						|
 | 
						|
      if (legal_number (path + 8, &fd) && fd == (int)fd)
 | 
						|
        {
 | 
						|
          r = fstat ((int)fd, finfo);
 | 
						|
          if (r == 0 || errno != EBADF)
 | 
						|
            return (r);
 | 
						|
        }
 | 
						|
      errno = ENOENT;
 | 
						|
      return (-1);
 | 
						|
#else
 | 
						|
  /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a
 | 
						|
     trailing slash.  Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx.
 | 
						|
     On most systems, with the notable exception of linux, this is
 | 
						|
     effectively a no-op. */
 | 
						|
      pbuf = xrealloc (pbuf, sizeof (DEV_FD_PREFIX) + strlen (path + 8));
 | 
						|
      strcpy (pbuf, DEV_FD_PREFIX);
 | 
						|
      strcat (pbuf, path + 8);
 | 
						|
      return (stat (pbuf, finfo));
 | 
						|
#endif /* !HAVE_DEV_FD */
 | 
						|
    }
 | 
						|
#if !defined (HAVE_DEV_STDIN)
 | 
						|
  else if (STREQN (path, "/dev/std", 8))
 | 
						|
    {
 | 
						|
      if (STREQ (path+8, "in"))
 | 
						|
	return (fstat (0, finfo));
 | 
						|
      else if (STREQ (path+8, "out"))
 | 
						|
	return (fstat (1, finfo));
 | 
						|
      else if (STREQ (path+8, "err"))
 | 
						|
	return (fstat (2, finfo));
 | 
						|
      else
 | 
						|
	return (stat (path, finfo));
 | 
						|
    }
 | 
						|
#endif /* !HAVE_DEV_STDIN */
 | 
						|
  return (stat (path, finfo));
 | 
						|
}
 | 
						|
 | 
						|
/* Do the same thing access(2) does, but use the effective uid and gid,
 | 
						|
   and don't make the mistake of telling root that any file is
 | 
						|
   executable.  This version uses stat(2). */
 | 
						|
static int
 | 
						|
sh_stataccess (path, mode)
 | 
						|
     char *path;
 | 
						|
     int mode;
 | 
						|
{
 | 
						|
  struct stat st;
 | 
						|
 | 
						|
  if (sh_stat (path, &st) < 0)
 | 
						|
    return (-1);
 | 
						|
 | 
						|
  if (current_user.euid == 0)
 | 
						|
    {
 | 
						|
      /* Root can read or write any file. */
 | 
						|
      if ((mode & X_OK) == 0)
 | 
						|
	return (0);
 | 
						|
 | 
						|
      /* Root can execute any file that has any one of the execute
 | 
						|
	 bits set. */
 | 
						|
      if (st.st_mode & S_IXUGO)
 | 
						|
	return (0);
 | 
						|
    }
 | 
						|
 | 
						|
  if (st.st_uid == current_user.euid)	/* owner */
 | 
						|
    mode <<= 6;
 | 
						|
  else if (group_member (st.st_gid))
 | 
						|
    mode <<= 3;
 | 
						|
 | 
						|
  if (st.st_mode & mode)
 | 
						|
    return (0);
 | 
						|
 | 
						|
  errno = EACCES;
 | 
						|
  return (-1);
 | 
						|
}
 | 
						|
 | 
						|
#if HAVE_DECL_SETREGID
 | 
						|
/* Version to call when uid != euid or gid != egid.  We temporarily swap
 | 
						|
   the effective and real uid and gid as appropriate. */
 | 
						|
static int
 | 
						|
sh_euidaccess (path, mode)
 | 
						|
     char *path;
 | 
						|
     int mode;
 | 
						|
{
 | 
						|
  int r, e;
 | 
						|
 | 
						|
  if (current_user.uid != current_user.euid)
 | 
						|
    setreuid (current_user.euid, current_user.uid);
 | 
						|
  if (current_user.gid != current_user.egid)
 | 
						|
    setregid (current_user.egid, current_user.gid);
 | 
						|
 | 
						|
  r = access (path, mode);
 | 
						|
  e = errno;
 | 
						|
 | 
						|
  if (current_user.uid != current_user.euid)
 | 
						|
    setreuid (current_user.uid, current_user.euid);
 | 
						|
  if (current_user.gid != current_user.egid)
 | 
						|
    setregid (current_user.gid, current_user.egid);
 | 
						|
 | 
						|
  errno = e;
 | 
						|
  return r;  
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
int
 | 
						|
sh_eaccess (path, mode)
 | 
						|
     char *path;
 | 
						|
     int mode;
 | 
						|
{
 | 
						|
  int ret;
 | 
						|
 | 
						|
  if (path_is_devfd (path))
 | 
						|
    return (sh_stataccess (path, mode));
 | 
						|
 | 
						|
#if defined (HAVE_FACCESSAT) && defined (AT_EACCESS)
 | 
						|
  return (faccessat (AT_FDCWD, path, mode, AT_EACCESS));
 | 
						|
#elif defined (HAVE_EACCESS)		/* FreeBSD */
 | 
						|
  ret = eaccess (path, mode);	/* XXX -- not always correct for X_OK */
 | 
						|
#  if defined (__FreeBSD__)
 | 
						|
  if (ret == 0 && current_user.euid == 0 && mode == X_OK)
 | 
						|
    return (sh_stataccess (path, mode));
 | 
						|
#  endif
 | 
						|
  return ret;
 | 
						|
#elif defined (EFF_ONLY_OK)		/* SVR4(?), SVR4.2 */
 | 
						|
  return access (path, mode|EFF_ONLY_OK);
 | 
						|
#else
 | 
						|
  if (mode == F_OK)
 | 
						|
    return (sh_stataccess (path, mode));
 | 
						|
    
 | 
						|
#  if HAVE_DECL_SETREGID
 | 
						|
  if (current_user.uid != current_user.euid || current_user.gid != current_user.egid)
 | 
						|
    return (sh_euidaccess (path, mode));
 | 
						|
#  endif
 | 
						|
 | 
						|
  if (current_user.uid == current_user.euid && current_user.gid == current_user.egid)
 | 
						|
    {
 | 
						|
      ret = access (path, mode);
 | 
						|
#if defined (__FreeBSD__) || defined (SOLARIS)
 | 
						|
      if (ret == 0 && current_user.euid == 0 && mode == X_OK)
 | 
						|
	return (sh_stataccess (path, mode));
 | 
						|
#endif
 | 
						|
      return ret;
 | 
						|
      
 | 
						|
    }
 | 
						|
 | 
						|
  return (sh_stataccess (path, mode));
 | 
						|
#endif
 | 
						|
}
 |