1 /*
2  * Copyright (C) 1987 - 2002 Free Software Foundation, Inc.
3  *
4  * This file is based on stuff from GNU Bash 1.14.7, the Bourne Again SHell.
5  * Everything that was changed is marked with the word `CHANGED'.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program 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 this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02110-1301, USA.
20  */
21 
22 #include "sys.h"
23 #include "posixstat.h"
24 #include <pwd.h>
25 #include <unistd.h>
26 #include "bash.h"
27 
28 /* Use the type that was determined by configure. */
29 #define GID_T GETGROUPS_T
30 
31 /*
32  * CHANGED:
33  * Perhaps these need new configure.in entries.
34  * The following macro's are used in bash, and below:
35  */
36 #undef SHELL
37 #undef AFS
38 #undef NOGROUP
39 
40 /*
41  * CHANGED:
42  * - Added prototypes,
43  * - used ANSI function arguments,
44  * - made all functions static and
45  * - changed all occurences of 'char *' into 'char const*' where possible.
46  * - changed all occurences of 'gid_t' into 'GID_T'.
47  * - exported functions needed in which.c
48  */
49 static char* extract_colon_unit (char const* string, int* p_index);
50 
51 /*===========================================================================
52  *
53  * Everything below is from bash-4.3.
54  *
55  */
56 
57 /* From bash-4.3 / shell.h / line 113 */
58 /* Information about the current user. */
59 struct user_info {
60   uid_t uid, euid;
61   GID_T gid, egid;
62   char *user_name;
63   char *shell;          /* shell from the password file */
64   char *home_dir;
65 };
66 
67 /* From bash-4.3 / shell.c / line 116 */
68 /* Information about the current user. */
69 struct user_info current_user =
70 {
71   (uid_t)-1, (uid_t)-1, (GID_T)-1, (GID_T)-1,
72   (char *)NULL, (char *)NULL, (char *)NULL
73 };
74 
75 /* From bash-4.3 / general.h / line 153 */
76 #define FREE(s)  do { if (s) free (s); } while (0)
77 
78 /* From bash-4.3 / shell.c / line 1201 */
79 /* Fetch the current set of uids and gids and return 1 if we're running
80    setuid or setgid. */
81 int
uidget()82 uidget ()
83 {
84   uid_t u;
85 
86   u = getuid ();
87   if (current_user.uid != u)
88     {
89       FREE (current_user.user_name);
90       FREE (current_user.shell);
91       FREE (current_user.home_dir);
92       current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL;
93     }
94   current_user.uid = u;
95   current_user.gid = getgid ();
96   current_user.euid = geteuid ();
97   current_user.egid = getegid ();
98 
99   /* See whether or not we are running setuid or setgid. */
100   return (current_user.uid != current_user.euid) ||
101            (current_user.gid != current_user.egid);
102 }
103 
104 /* From bash-4.3 / general.c / line 1018 */
105 static int ngroups, maxgroups;
106 
107 /* From bash-4.3 / general.c / line 1020 */
108 /* The set of groups that this user is a member of. */
109 static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL;
110 
111 /* From bash-4.3 / general.c / line 1023 */
112 #if !defined (NOGROUP)
113 #  define NOGROUP (GID_T) -1
114 #endif
115 
116 /* From bash-4.3 / lib/sh/oslib.c / line 250 */
117 #define DEFAULT_MAXGROUPS 64
118 
119 /* From bash-4.3 / lib/sh/oslib.c / line 252 */
120 int
getmaxgroups()121 getmaxgroups ()
122 {
123   static int maxgroups = -1;
124 
125   if (maxgroups > 0)
126     return maxgroups;
127 
128 #if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX)
129   maxgroups = sysconf (_SC_NGROUPS_MAX);
130 #else
131 #  if defined (NGROUPS_MAX)
132   maxgroups = NGROUPS_MAX;
133 #  else /* !NGROUPS_MAX */
134 #    if defined (NGROUPS)
135   maxgroups = NGROUPS;
136 #    else /* !NGROUPS */
137   maxgroups = DEFAULT_MAXGROUPS;
138 #    endif /* !NGROUPS */
139 #  endif /* !NGROUPS_MAX */
140 #endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */
141 
142   if (maxgroups <= 0)
143     maxgroups = DEFAULT_MAXGROUPS;
144 
145   return maxgroups;
146 }
147 
148 /* From bash-4.3 / general.c / line 1027 */
149 static void
initialize_group_array()150 initialize_group_array ()
151 {
152   register int i;
153 
154   if (maxgroups == 0)
155     maxgroups = getmaxgroups ();
156 
157   ngroups = 0;
158   group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T));
159 
160 #if defined (HAVE_GETGROUPS)
161   ngroups = getgroups (maxgroups, group_array);
162 #endif
163 
164   /* If getgroups returns nothing, or the OS does not support getgroups(),
165      make sure the groups array includes at least the current gid. */
166   if (ngroups == 0)
167     {
168       group_array[0] = current_user.gid;
169       ngroups = 1;
170     }
171 
172   /* If the primary group is not in the groups array, add it as group_array[0]
173      and shuffle everything else up 1, if there's room. */
174   for (i = 0; i < ngroups; i++)
175     if (current_user.gid == (GID_T)group_array[i])
176       break;
177   if (i == ngroups && ngroups < maxgroups)
178     {
179       for (i = ngroups; i > 0; i--)
180         group_array[i] = group_array[i - 1];
181       group_array[0] = current_user.gid;
182       ngroups++;
183     }
184 
185   /* If the primary group is not group_array[0], swap group_array[0] and
186      whatever the current group is.  The vast majority of systems should
187      not need this; a notable exception is Linux. */
188   if (group_array[0] != current_user.gid)
189     {
190       for (i = 0; i < ngroups; i++)
191         if (group_array[i] == current_user.gid)
192           break;
193       if (i < ngroups)
194         {
195           group_array[i] = group_array[0];
196           group_array[0] = current_user.gid;
197         }
198     }
199 }
200 
201 /* From bash-4.3 / general.c / line 1079 */
202 /* Return non-zero if GID is one that we have in our groups list. */
203 int
204 #if defined (__STDC__) || defined ( _MINIX)
group_member(GID_T gid)205 group_member (GID_T gid)
206 #else
207 group_member (gid)
208      GID_T gid;
209 #endif /* !__STDC__ && !_MINIX */
210 {
211 #if defined (HAVE_GETGROUPS)
212   register int i;
213 #endif
214 
215   /* Short-circuit if possible, maybe saving a call to getgroups(). */
216   if (gid == current_user.gid || gid == current_user.egid)
217     return (1);
218 
219 #if defined (HAVE_GETGROUPS)
220   if (ngroups == 0)
221     initialize_group_array ();
222 
223   /* In case of error, the user loses. */
224   if (ngroups <= 0)
225     return (0);
226 
227   /* Search through the list looking for GID. */
228   for (i = 0; i < ngroups; i++)
229     if (gid == (GID_T)group_array[i])
230       return (1);
231 #endif
232 
233   return (0);
234 }
235 
236 /* From bash-4.3 / findcmd.c / line 80 */
237 /* Return some flags based on information about this file.
238    The EXISTS bit is non-zero if the file is found.
239    The EXECABLE bit is non-zero the file is executble.
240    Zero is returned if the file is not found. */
241 int
file_status(char const * name)242 file_status (char const* name)
243 {
244   struct stat finfo;
245   int r;
246 
247   /* Determine whether this file exists or not. */
248   if (stat (name, &finfo) < 0)
249     return (0);
250 
251   /* If the file is a directory, then it is not "executable" in the
252      sense of the shell. */
253   if (S_ISDIR (finfo.st_mode))
254     return (FS_EXISTS|FS_DIRECTORY);
255 
256   r = FS_EXISTS;
257 
258 #if defined (HAVE_EACCESS)
259   /* Use eaccess(2) if we have it to take things like ACLs and other
260      file access mechanisms into account.  eaccess uses the effective
261      user and group IDs, not the real ones.  We could use sh_eaccess,
262      but we don't want any special treatment for /dev/fd. */
263   if (eaccess (name, X_OK) == 0)
264     r |= FS_EXECABLE;
265   if (eaccess (name, R_OK) == 0)
266     r |= FS_READABLE;
267 
268   return r;
269 #elif defined (AFS)
270   /* We have to use access(2) to determine access because AFS does not
271      support Unix file system semantics.  This may produce wrong
272      answers for non-AFS files when ruid != euid.  I hate AFS. */
273   if (access (name, X_OK) == 0)
274     r |= FS_EXECABLE;
275   if (access (name, R_OK) == 0)
276     r |= FS_READABLE;
277 
278   return r;
279 #else /* !AFS */
280 
281   /* Find out if the file is actually executable.  By definition, the
282      only other criteria is that the file has an execute bit set that
283      we can use.  The same with whether or not a file is readable. */
284 
285   /* Root only requires execute permission for any of owner, group or
286      others to be able to exec a file, and can read any file. */
287   if (current_user.euid == (uid_t)0)
288     {
289       r |= FS_READABLE;
290       if (finfo.st_mode & S_IXUGO)
291 	r |= FS_EXECABLE;
292       return r;
293     }
294 
295   /* If we are the owner of the file, the owner bits apply. */
296   if (current_user.euid == finfo.st_uid)
297     {
298       if (finfo.st_mode & S_IXUSR)
299 	r |= FS_EXECABLE;
300       if (finfo.st_mode & S_IRUSR)
301 	r |= FS_READABLE;
302     }
303 
304   /* If we are in the owning group, the group permissions apply. */
305   else if (group_member (finfo.st_gid))
306     {
307       if (finfo.st_mode & S_IXGRP)
308 	r |= FS_EXECABLE;
309       if (finfo.st_mode & S_IRGRP)
310 	r |= FS_READABLE;
311     }
312 
313   /* Else we check whether `others' have permission to execute the file */
314   else
315     {
316       if (finfo.st_mode & S_IXOTH)
317 	r |= FS_EXECABLE;
318       if (finfo.st_mode & S_IROTH)
319 	r |= FS_READABLE;
320     }
321 
322   return r;
323 #endif /* !AFS */
324 }
325 
326 /* From bash-4.3 / general.c / line 604 ; Changes: Using 'strchr' instead of 'mbschr'. */
327 /* Return 1 if STRING is an absolute program name; it is absolute if it
328    contains any slashes.  This is used to decide whether or not to look
329    up through $PATH. */
330 int
absolute_program(char const * string)331 absolute_program (char const* string)
332 {
333   return ((char *)strchr (string, '/') != (char *)NULL);
334 }
335 
336 /* From bash-4.3 / stringlib.c / line 124 */
337 /* Cons a new string from STRING starting at START and ending at END,
338    not including END. */
339 char *
substring(char const * string,int start,int end)340 substring (char const* string, int start, int end)
341 {
342   register int len;
343   register char *result;
344 
345   len = end - start;
346   result = (char *)xmalloc (len + 1);
347   memcpy (result, string + start, len);
348   result[len] = '\0';
349   return (result);
350 }
351 
352 /* From bash-4.3 / general.c / line 780 ; changes: Return NULL instead of 'string' when string == 0. */
353 /* Given a string containing units of information separated by colons,
354    return the next one pointed to by (P_INDEX), or NULL if there are no more.
355    Advance (P_INDEX) to the character after the colon. */
356 char*
extract_colon_unit(char const * string,int * p_index)357 extract_colon_unit (char const* string, int* p_index)
358 {
359   int i, start, len;
360   char *value;
361 
362   if (string == 0)
363     return NULL;
364 
365   len = strlen (string);
366   if (*p_index >= len)
367     return ((char *)NULL);
368 
369   i = *p_index;
370 
371   /* Each call to this routine leaves the index pointing at a colon if
372      there is more to the path.  If I is > 0, then increment past the
373      `:'.  If I is 0, then the path has a leading colon.  Trailing colons
374      are handled OK by the `else' part of the if statement; an empty
375      string is returned in that case. */
376   if (i && string[i] == ':')
377     i++;
378 
379   for (start = i; string[i] && string[i] != ':'; i++)
380     ;
381 
382   *p_index = i;
383 
384   if (i == start)
385     {
386       if (string[i])
387         (*p_index)++;
388       /* Return "" in the case of a trailing `:'. */
389       value = (char *)xmalloc (1);
390       value[0] = '\0';
391     }
392   else
393     value = substring (string, start, i);
394 
395   return (value);
396 }
397 
398 /* From bash-4.3 / findcmd.c / line 273 */
399 /* Return the next element from PATH_LIST, a colon separated list of
400    paths.  PATH_INDEX_POINTER is the address of an index into PATH_LIST;
401    the index is modified by this function.
402    Return the next element of PATH_LIST or NULL if there are no more. */
403 char*
get_next_path_element(char const * path_list,int * path_index_pointer)404 get_next_path_element (char const* path_list, int* path_index_pointer)
405 {
406   char* path;
407 
408   path = extract_colon_unit (path_list, path_index_pointer);
409 
410   if (path == 0)
411     return (path);
412 
413   if (*path == '\0')
414     {
415       free (path);
416       path = savestring (".");
417     }
418 
419   return (path);
420 }
421 
422 /* From bash-1.14.7 */
423 /* Turn PATH, a directory, and NAME, a filename, into a full pathname.
424    This allocates new memory and returns it. */
425 char *
make_full_pathname(const char * path,const char * name,int name_len)426 make_full_pathname (const char *path, const char *name, int name_len)
427 {
428   char *full_path;
429   int path_len;
430 
431   path_len = strlen (path);
432   full_path = (char *) xmalloc (2 + path_len + name_len);
433   strcpy (full_path, path);
434   full_path[path_len] = '/';
435   strcpy (full_path + path_len + 1, name);
436   return (full_path);
437 }
438 
439 /* From bash-4.3 / shell.c / line 1659 */
440 void
get_current_user_info()441 get_current_user_info ()
442 {
443   struct passwd *entry;
444 
445   /* Don't fetch this more than once. */
446   if (current_user.user_name == 0)
447     {
448 #if defined (__TANDEM)
449       entry = getpwnam (getlogin ());
450 #else
451       entry = getpwuid (current_user.uid);
452 #endif
453       if (entry)
454         {
455           current_user.user_name = savestring (entry->pw_name);
456           current_user.shell = (entry->pw_shell && entry->pw_shell[0])
457                                 ? savestring (entry->pw_shell)
458                                 : savestring ("/bin/sh");
459           current_user.home_dir = savestring (entry->pw_dir);
460         }
461       else
462         {
463           current_user.user_name = "I have no name!";
464           current_user.user_name = savestring (current_user.user_name);
465           current_user.shell = savestring ("/bin/sh");
466           current_user.home_dir = savestring ("/");
467         }
468       endpwent ();
469     }
470 }
471 
472 /* This is present for use by the tilde library. */
sh_get_env_value(const char * v)473 char* sh_get_env_value (const char* v)
474 {
475   return getenv(v);
476 }
477 
sh_get_home_dir(void)478 char* sh_get_home_dir(void)
479 {
480   if (current_user.home_dir == NULL)
481     get_current_user_info();
482   return current_user.home_dir;
483 }
484 
485