Viewing file: pathchk.c (8.92 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* pathchk - check pathnames for validity and portability */
/* Usage: pathchk [-p] path ...
For each PATH, print a message if any of these conditions are false: * all existing leading directories in PATH have search (execute) permission * strlen (PATH) <= PATH_MAX * strlen (each_directory_in_PATH) <= NAME_MAX
Exit status: 0 All PATH names passed all of the tests. 1 An error occurred.
Options: -p Instead of performing length checks on the underlying filesystem, test the length of the pathname and its components against the POSIX.1 minimum limits for portability, _POSIX_NAME_MAX and _POSIX_PATH_MAX in 2.9.2. Also check that the pathname contains no character not in the portable filename character set. */
/* See Makefile for compilation details. */
#include <config.h>
#include <sys/types.h> #include "posixstat.h"
#if defined (HAVE_UNISTD_H) # include <unistd.h> #endif
#if defined (HAVE_LIMITS_H) # include <limits.h> #endif
#include "bashansi.h"
#include <stdio.h> #include <errno.h>
#include "builtins.h" #include "shell.h" #include "stdc.h" #include "bashgetopt.h" #include "maxpath.h"
#if !defined (errno) extern int errno; #endif
#if !defined (_POSIX_PATH_MAX) # define _POSIX_PATH_MAX 255 #endif #if !defined (_POSIX_NAME_MAX) # define _POSIX_NAME_MAX 14 #endif
/* How do we get PATH_MAX? */ #if defined (_POSIX_VERSION) && !defined (PATH_MAX) # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX) #endif
/* How do we get NAME_MAX? */ #if defined (_POSIX_VERSION) && !defined (NAME_MAX) # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX) #endif
#if !defined (PATH_MAX_FOR) # define PATH_MAX_FOR(p) PATH_MAX #endif
#if !defined (NAME_MAX_FOR) # define NAME_MAX_FOR(p) NAME_MAX #endif
extern char *strerror ();
static int validate_path ();
pathchk_builtin (list) WORD_LIST *list; { int retval, pflag, opt;
reset_internal_getopt (); while ((opt = internal_getopt (list, "p")) != -1) { switch (opt) { case 'p': pflag = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend;
if (list == 0) { builtin_usage (); return (EX_USAGE); }
for (retval = 0; list; list = list->next) retval |= validate_path (list->word->word, pflag);
return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS); }
char *pathchk_doc[] = { "Check each pathname argument for validity (i.e., it may be used to", "create or access a file without casuing syntax errors) and portability", "(i.e., no filename truncation will result). If the `-p' option is", "supplied, more extensive portability checks are performed.", (char *)NULL };
/* The standard structure describing a builtin command. bash keeps an array of these structures. */ struct builtin pathchk_struct = { "pathchk", /* builtin name */ pathchk_builtin, /* function implementing the builtin */ BUILTIN_ENABLED, /* initial flags for builtin */ pathchk_doc, /* array of long documentation strings. */ "pathchk [-p] pathname ...", /* usage synopsis */ 0 /* reserved for internal use */ };
/* The remainder of this file is stolen shamelessly from `pathchk.c' in the sh-utils-1.12 distribution, by
David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering <meyering@cs.utexas.edu> */
/* Each element is nonzero if the corresponding ASCII character is in the POSIX portable character set, and zero if it is not. In addition, the entry for `/' is nonzero to simplify checking. */ static char const portable_chars[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
/* If PATH contains only portable characters, return 1, else 0. */
static int portable_chars_only (path) const char *path; { const char *p;
for (p = path; *p; ++p) if (portable_chars[(const unsigned char) *p] == 0) { builtin_error ("path `%s' contains nonportable character `%c'", path, *p); return 0; } return 1; }
/* On some systems, stat can return EINTR. */
#ifndef EINTR # define SAFE_STAT(name, buf) stat (name, buf) #else # define SAFE_STAT(name, buf) safe_stat (name, buf) static inline int safe_stat (name, buf) const char *name; struct stat *buf; { int ret;
do ret = stat (name, buf); while (ret < 0 && errno == EINTR);
return ret; } #endif
/* Return 1 if PATH is a usable leading directory, 0 if not, 2 if it doesn't exist. */
static int dir_ok (path) const char *path; { struct stat stats;
if (SAFE_STAT (path, &stats)) return 2;
if (!S_ISDIR (stats.st_mode)) { builtin_error ("`%s' is not a directory", path); return 0; }
/* Use access to test for search permission because testing permission bits of st_mode can lose with new access control mechanisms. Of course, access loses if you're running setuid. */ if (access (path, X_OK) != 0) { if (errno == EACCES) builtin_error ("directory `%s' is not searchable", path); else builtin_error ("%s: %s", path, strerror (errno)); return 0; }
return 1; }
static char * xstrdup (s) char *s; { return (savestring (s)); }
/* Make sure that strlen (PATH) <= PATH_MAX && strlen (each-existing-directory-in-PATH) <= NAME_MAX
If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and _POSIX_NAME_MAX instead, and make sure that PATH contains no characters not in the POSIX portable filename character set, which consists of A-Z, a-z, 0-9, ., _, -.
Make sure that all leading directories along PATH that exist have `x' permission.
Return 0 if all of these tests are successful, 1 if any fail. */
static int validate_path (path, portability) char *path; int portability; { int path_max; int last_elem; /* Nonzero if checking last element of path. */ int exists; /* 2 if the path element exists. */ char *slash; char *parent; /* Last existing leading directory so far. */
if (portability && !portable_chars_only (path)) return 1;
if (*path == '\0') return 0;
#ifdef lint /* Suppress `used before initialized' warning. */ exists = 0; #endif
/* Figure out the parent of the first element in PATH. */ parent = xstrdup (*path == '/' ? "/" : ".");
slash = path; last_elem = 0; while (1) { int name_max; int length; /* Length of partial path being checked. */ char *start; /* Start of path element being checked. */
/* Find the end of this element of the path. Then chop off the rest of the path after this element. */ while (*slash == '/') slash++; start = slash; slash = strchr (slash, '/'); if (slash != NULL) *slash = '\0'; else { last_elem = 1; slash = strchr (start, '\0'); }
if (!last_elem) { exists = dir_ok (path); if (dir_ok == 0) { free (parent); return 1; } }
length = slash - start; /* Since we know that `parent' is a directory, it's ok to call pathconf with it as the argument. (If `parent' isn't a directory or doesn't exist, the behavior of pathconf is undefined.) But if `parent' is a directory and is on a remote file system, it's likely that pathconf can't give us a reasonable value and will return -1. (NFS and tempfs are not POSIX . . .) In that case, we have no choice but to assume the pessimal POSIX minimums. */ name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent); if (name_max < 0) name_max = _POSIX_NAME_MAX; if (length > name_max) { builtin_error ("name `%s' has length %d; exceeds limit of %d", start, length, name_max); free (parent); return 1; }
if (last_elem) break;
if (exists == 1) { free (parent); parent = xstrdup (path); }
*slash++ = '/'; }
/* `parent' is now the last existing leading directory in the whole path, so it's ok to call pathconf with it as the argument. */ path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent); if (path_max < 0) path_max = _POSIX_PATH_MAX; free (parent); if (strlen (path) > path_max) { builtin_error ("path `%s' has length %d; exceeds limit of %d", path, strlen (path), path_max); return 1; }
return 0; }
|