1/* eaccess.c - eaccess replacement for the shell, plus other access functions. */
2
3/* Copyright (C) 2006 Free Software Foundation, Inc.
4
5   This file is part of GNU Bash, the Bourne Again SHell.
6
7   Bash is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   Bash is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#if defined (HAVE_CONFIG_H)
22#  include <config.h>
23#endif
24
25#include <stdio.h>
26
27#include "bashtypes.h"
28
29#if defined (HAVE_UNISTD_H)
30#  include <unistd.h>
31#endif
32
33#include "bashansi.h"
34
35#include <errno.h>
36#if !defined (errno)
37extern int errno;
38#endif /* !errno */
39
40#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H)
41#  include <sys/file.h>
42#endif /* !_POSIX_VERSION */
43#include "posixstat.h"
44#include "filecntl.h"
45
46#include "shell.h"
47
48#if !defined (R_OK)
49#define R_OK 4
50#define W_OK 2
51#define X_OK 1
52#define F_OK 0
53#endif /* R_OK */
54
55static int path_is_devfd __P((const char *));
56static int sh_stataccess __P((char *, int));
57#if HAVE_DECL_SETREGID
58static int sh_euidaccess __P((char *, int));
59#endif
60
61static int
62path_is_devfd (path)
63     const char *path;
64{
65  if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0)
66    return 1;
67  else if (STREQN (path, "/dev/std", 8))
68    {
69      if (STREQ (path+8, "in") || STREQ (path+8, "out") || STREQ (path+8, "err"))
70	return 1;
71      else
72	return 0;
73    }
74  else
75    return 0;
76}
77
78/* A wrapper for stat () which disallows pathnames that are empty strings
79   and handles /dev/fd emulation on systems that don't have it. */
80int
81sh_stat (path, finfo)
82     const char *path;
83     struct stat *finfo;
84{
85  if (*path == '\0')
86    {
87      errno = ENOENT;
88      return (-1);
89    }
90  if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0)
91    {
92#if !defined (HAVE_DEV_FD)
93      intmax_t fd;
94      int r;
95
96      if (legal_number (path + 8, &fd) && fd == (int)fd)
97        {
98          r = fstat ((int)fd, finfo);
99          if (r == 0 || errno != EBADF)
100            return (r);
101        }
102      errno = ENOENT;
103      return (-1);
104#else
105  /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a
106     trailing slash.  Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx.
107     On most systems, with the notable exception of linux, this is
108     effectively a no-op. */
109      char pbuf[32];
110      strcpy (pbuf, DEV_FD_PREFIX);
111      strcat (pbuf, path + 8);
112      return (stat (pbuf, finfo));
113#endif /* !HAVE_DEV_FD */
114    }
115#if !defined (HAVE_DEV_STDIN)
116  else if (STREQN (path, "/dev/std", 8))
117    {
118      if (STREQ (path+8, "in"))
119	return (fstat (0, finfo));
120      else if (STREQ (path+8, "out"))
121	return (fstat (1, finfo));
122      else if (STREQ (path+8, "err"))
123	return (fstat (2, finfo));
124      else
125	return (stat (path, finfo));
126    }
127#endif /* !HAVE_DEV_STDIN */
128  return (stat (path, finfo));
129}
130
131/* Do the same thing access(2) does, but use the effective uid and gid,
132   and don't make the mistake of telling root that any file is
133   executable.  This version uses stat(2). */
134static int
135sh_stataccess (path, mode)
136     char *path;
137     int mode;
138{
139  struct stat st;
140
141  if (sh_stat (path, &st) < 0)
142    return (-1);
143
144  if (current_user.euid == 0)
145    {
146      /* Root can read or write any file. */
147      if ((mode & X_OK) == 0)
148	return (0);
149
150      /* Root can execute any file that has any one of the execute
151	 bits set. */
152      if (st.st_mode & S_IXUGO)
153	return (0);
154    }
155
156  if (st.st_uid == current_user.euid)	/* owner */
157    mode <<= 6;
158  else if (group_member (st.st_gid))
159    mode <<= 3;
160
161  if (st.st_mode & mode)
162    return (0);
163
164  errno = EACCES;
165  return (-1);
166}
167
168#if HAVE_DECL_SETREGID
169/* Version to call when uid != euid or gid != egid.  We temporarily swap
170   the effective and real uid and gid as appropriate. */
171static int
172sh_euidaccess (path, mode)
173     char *path;
174     int mode;
175{
176  int r, e;
177
178  if (current_user.uid != current_user.euid)
179    setreuid (current_user.euid, current_user.uid);
180  if (current_user.gid != current_user.egid)
181    setregid (current_user.egid, current_user.gid);
182
183  r = access (path, mode);
184  e = errno;
185
186  if (current_user.uid != current_user.euid)
187    setreuid (current_user.uid, current_user.euid);
188  if (current_user.gid != current_user.egid)
189    setregid (current_user.gid, current_user.egid);
190
191  errno = e;
192  return r;
193}
194#endif
195
196int
197sh_eaccess (path, mode)
198     char *path;
199     int mode;
200{
201  if (path_is_devfd (path))
202    return (sh_stataccess (path, mode));
203
204#if defined (HAVE_EACCESS)		/* FreeBSD */
205  return (eaccess (path, mode));
206#elif defined (EFF_ONLY_OK)		/* SVR4(?), SVR4.2 */
207  return access (path, mode|EFF_ONLY_OK);
208#else
209  if (mode == F_OK)
210    return (sh_stataccess (path, mode));
211
212#  if HAVE_DECL_SETREGID
213  if (current_user.uid != current_user.euid || current_user.gid != current_user.egid)
214    return (sh_euidaccess (path, mode));
215#  endif
216
217  if (current_user.uid == current_user.euid && current_user.gid == current_user.egid)
218    return (access (path, mode));
219
220  return (sh_stataccess (path, mode));
221#endif
222}
223