1 /* eaccess.c - eaccess replacement for the shell, plus other access functions. */
2
3 /* Copyright (C) 2006-2020 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)
37 extern 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
55 static int path_is_devfd PARAMS((const char *));
56 static int sh_stataccess PARAMS((const char *, int));
57 #if HAVE_DECL_SETREGID
58 static int sh_euidaccess PARAMS((const char *, int));
59 #endif
60
61 static int
path_is_devfd(path)62 path_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. */
80 int
sh_stat(path,finfo)81 sh_stat (path, finfo)
82 const char *path;
83 struct stat *finfo;
84 {
85 static char *pbuf = 0;
86
87 if (*path == '\0')
88 {
89 errno = ENOENT;
90 return (-1);
91 }
92 if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0)
93 {
94 /* If stating /dev/fd/n doesn't produce the same results as fstat of
95 FD N, then define DEV_FD_STAT_BROKEN */
96 #if !defined (HAVE_DEV_FD) || defined (DEV_FD_STAT_BROKEN)
97 intmax_t fd;
98 int r;
99
100 if (legal_number (path + 8, &fd) && fd == (int)fd)
101 {
102 r = fstat ((int)fd, finfo);
103 if (r == 0 || errno != EBADF)
104 return (r);
105 }
106 errno = ENOENT;
107 return (-1);
108 #else
109 /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a
110 trailing slash. Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx.
111 On most systems, with the notable exception of linux, this is
112 effectively a no-op. */
113 pbuf = xrealloc (pbuf, sizeof (DEV_FD_PREFIX) + strlen (path + 8));
114 strcpy (pbuf, DEV_FD_PREFIX);
115 strcat (pbuf, path + 8);
116 return (stat (pbuf, finfo));
117 #endif /* !HAVE_DEV_FD */
118 }
119 #if !defined (HAVE_DEV_STDIN)
120 else if (STREQN (path, "/dev/std", 8))
121 {
122 if (STREQ (path+8, "in"))
123 return (fstat (0, finfo));
124 else if (STREQ (path+8, "out"))
125 return (fstat (1, finfo));
126 else if (STREQ (path+8, "err"))
127 return (fstat (2, finfo));
128 else
129 return (stat (path, finfo));
130 }
131 #endif /* !HAVE_DEV_STDIN */
132 return (stat (path, finfo));
133 }
134
135 /* Do the same thing access(2) does, but use the effective uid and gid,
136 and don't make the mistake of telling root that any file is
137 executable. This version uses stat(2). */
138 static int
sh_stataccess(path,mode)139 sh_stataccess (path, mode)
140 const char *path;
141 int mode;
142 {
143 struct stat st;
144
145 if (sh_stat (path, &st) < 0)
146 return (-1);
147
148 if (current_user.euid == 0)
149 {
150 /* Root can read or write any file. */
151 if ((mode & X_OK) == 0)
152 return (0);
153
154 /* Root can execute any file that has any one of the execute
155 bits set. */
156 if (st.st_mode & S_IXUGO)
157 return (0);
158 }
159
160 if (st.st_uid == current_user.euid) /* owner */
161 mode <<= 6;
162 else if (group_member (st.st_gid))
163 mode <<= 3;
164
165 if (st.st_mode & mode)
166 return (0);
167
168 errno = EACCES;
169 return (-1);
170 }
171
172 #if HAVE_DECL_SETREGID
173 /* Version to call when uid != euid or gid != egid. We temporarily swap
174 the effective and real uid and gid as appropriate. */
175 static int
sh_euidaccess(path,mode)176 sh_euidaccess (path, mode)
177 const char *path;
178 int mode;
179 {
180 int r, e;
181
182 if (current_user.uid != current_user.euid)
183 setreuid (current_user.euid, current_user.uid);
184 if (current_user.gid != current_user.egid)
185 setregid (current_user.egid, current_user.gid);
186
187 r = access (path, mode);
188 e = errno;
189
190 if (current_user.uid != current_user.euid)
191 setreuid (current_user.uid, current_user.euid);
192 if (current_user.gid != current_user.egid)
193 setregid (current_user.gid, current_user.egid);
194
195 errno = e;
196 return r;
197 }
198 #endif
199
200 int
sh_eaccess(path,mode)201 sh_eaccess (path, mode)
202 const char *path;
203 int mode;
204 {
205 int ret;
206
207 if (path_is_devfd (path))
208 return (sh_stataccess (path, mode));
209
210 #if (defined (HAVE_FACCESSAT) && defined (AT_EACCESS)) || defined (HAVE_EACCESS)
211 # if defined (HAVE_FACCESSAT) && defined (AT_EACCESS)
212 ret = faccessat (AT_FDCWD, path, mode, AT_EACCESS);
213 # else /* HAVE_EACCESS */ /* FreeBSD */
214 ret = eaccess (path, mode); /* XXX -- not always correct for X_OK */
215 # endif /* HAVE_EACCESS */
216 # if defined (__FreeBSD__) || defined (SOLARIS) || defined (_AIX)
217 if (ret == 0 && current_user.euid == 0 && mode == X_OK)
218 return (sh_stataccess (path, mode));
219 # endif /* __FreeBSD__ || SOLARIS || _AIX */
220 return ret;
221 #elif defined (EFF_ONLY_OK) /* SVR4(?), SVR4.2 */
222 return access (path, mode|EFF_ONLY_OK);
223 #else
224 if (mode == F_OK)
225 return (sh_stataccess (path, mode));
226
227 # if HAVE_DECL_SETREGID
228 if (current_user.uid != current_user.euid || current_user.gid != current_user.egid)
229 return (sh_euidaccess (path, mode));
230 # endif
231
232 if (current_user.uid == current_user.euid && current_user.gid == current_user.egid)
233 {
234 ret = access (path, mode);
235 #if defined (__FreeBSD__) || defined (SOLARIS)
236 if (ret == 0 && current_user.euid == 0 && mode == X_OK)
237 return (sh_stataccess (path, mode));
238 #endif
239 return ret;
240 }
241
242 return (sh_stataccess (path, mode));
243 #endif
244 }
245