which.c (3350B)
1 /* which.c: check to see if a file is executable. 2 3 This function was originally written with Maarten Litmaath's which.c as 4 a template, but was changed in order to accommodate the possibility of 5 rc's running setuid or the possibility of executing files not in the 6 primary group. Much of this file has been re-vamped by Paul Haahr. 7 I re-re-vamped the functions that Paul supplied to correct minor bugs 8 and to strip out unneeded functionality. 9 */ 10 11 #include "rc.h" 12 13 #include <ctype.h> 14 #include <errno.h> 15 #include <sys/stat.h> 16 17 #include "getgroups.h" 18 19 #define X_USR 0100 20 #define X_GRP 0010 21 #define X_OTH 0001 22 #define X_ALL (X_USR|X_GRP|X_OTH) 23 24 static bool initialized = FALSE; 25 static uid_t uid; 26 static gid_t gid; 27 28 #if HAVE_GETGROUPS 29 static int ngroups; 30 static GETGROUPS_T *gidset; 31 32 /* determine whether gid lies in gidset */ 33 34 static int ingidset(gid_t g) { 35 int i; 36 for (i = 0; i < ngroups; ++i) 37 if (g == gidset[i]) 38 return 1; 39 return 0; 40 } 41 #else 42 #define ingidset(g) (FALSE) 43 #endif 44 45 /* 46 A home-grown access/stat. Does the right thing for group-executable files. 47 Returns a bool instead of this -1 nonsense. 48 */ 49 50 bool rc_access(char *path, bool verbose, struct stat *stp) { 51 int mask; 52 if (stat(path, stp) != 0) { 53 if (verbose) /* verbose flag only set for absolute pathname */ 54 uerror(path); 55 return FALSE; 56 } 57 if (uid == 0) 58 mask = X_ALL; 59 else if (uid == stp->st_uid) 60 mask = X_USR; 61 else if (gid == stp->st_gid || ingidset(stp->st_gid)) 62 mask = X_GRP; 63 else 64 mask = X_OTH; 65 if (((stp->st_mode & S_IFMT) == S_IFREG) && (stp->st_mode & mask)) 66 return TRUE; 67 errno = EACCES; 68 if (verbose) 69 uerror(path); 70 return FALSE; 71 } 72 73 /* replace non-printing characters with question marks in a freshly 74 * allocated string */ 75 static char *protect(char *in) { 76 int l = strlen(in); 77 char *out = ealloc(l + 1); 78 int i; 79 80 for (i = 0; i < l; ++i) 81 out[i] = isprint(in[i]) ? in[i] : '?'; 82 out[i] = '\0'; 83 return out; 84 } 85 86 /* return a full pathname by searching $path, and by checking the status of the file */ 87 88 extern char *which(char *name, bool verbose) { 89 static char *test = NULL; 90 static size_t testlen = 0; 91 List *path; 92 int len; 93 struct stat st; 94 if (name == NULL) /* no filename? can happen with "> foo" as a command */ 95 return NULL; 96 if (!initialized) { 97 initialized = TRUE; 98 uid = geteuid(); 99 gid = getegid(); 100 #if HAVE_GETGROUPS 101 #if HAVE_POSIX_GETGROUPS 102 ngroups = getgroups(0, (GETGROUPS_T *)0); 103 if (ngroups < 0) { 104 uerror("getgroups"); 105 rc_exit(1); 106 } 107 #else 108 ngroups = NGROUPS; 109 #endif 110 if (ngroups) { 111 gidset = ealloc(ngroups * sizeof(GETGROUPS_T)); 112 getgroups(ngroups, gidset); 113 } 114 #endif 115 } 116 if (isabsolute(name)) /* absolute pathname? */ 117 return rc_access(name, verbose, &st) ? name : NULL; 118 len = strlen(name); 119 for (path = varlookup("path"); path != NULL; path = path->n) { 120 size_t need = strlen(path->w) + len + 2; /* one for null terminator, one for the '/' */ 121 if (testlen < need) { 122 efree(test); 123 test = ealloc(testlen = need); 124 } 125 if (*path->w == '\0') { 126 strcpy(test, name); 127 } else { 128 strcpy(test, path->w); 129 if (!streq(test, "/")) /* "//" is special to POSIX */ 130 strcat(test, "/"); 131 strcat(test, name); 132 } 133 if (rc_access(test, FALSE, &st)) 134 return test; 135 } 136 if (verbose) { 137 char *n = protect(name); 138 fprint(2, RC "cannot find `%s'\n", n); 139 efree(n); 140 } 141 return NULL; 142 }