1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright (c) 1994 by Sun Microsystems, Inc.
  24  * Copyright (c) 2018, Joyent, Inc.
  25  */
  26 
  27 /*
  28  * Copyright 1991, 1992 by Mortice Kern Systems Inc.  All rights reserved.
  29  *
  30  * Standards Conformance :
  31  *      P1003.2/D11.2
  32  *
  33  */
  34 /*
  35  * Original ident string for reference
  36  * ident        "$Id: pathchk.c,v 1.29 1994/05/24 15:51:19 mark Exp $"
  37  */
  38 
  39 #include <locale.h>
  40 #include <libintl.h>
  41 #include <limits.h>
  42 #include <sys/stat.h>
  43 #include <fcntl.h>                /* for creat() prototype */
  44 #include <string.h>
  45 #include <errno.h>
  46 #include <stdlib.h>
  47 #include <stdio.h>
  48 #include <ctype.h>
  49 #include <unistd.h>
  50 #include <stdlib.h>
  51 
  52 /*
  53  * These are the characters in the portable filename character set defined
  54  * in POSIX P1003.2.
  55  */
  56 static  char    portfsset[] = \
  57         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-";
  58 
  59 
  60 #ifndef M_FSDELIM
  61 #define M_FSDELIM(c)    ((c) == '/')
  62 #endif
  63 
  64 static char *nametoolong = "%s: component too long.\n";
  65 static char *pathtoolong = "%s: pathname too long.\n";
  66 static char *notsrch = "%s: Not searchable.\n";
  67 static char *badchar = "%s: Nonportable character '%c' (%#02X) found.\n";
  68 static char *badbyte = "%s: Nonportable byte %#02X found.\n";
  69 
  70 static char *pathconfprob = "pathchk: warning: \
  71                             pathconf(\"%s\", %s) returns '%s'. Using %s = %d\n";
  72 
  73 
  74 static int printWarnings = 1;
  75 
  76 static int checkpathname(char *, int);
  77 static void usage(void);
  78 
  79 /*
  80  * mainline for pathchk
  81  */
  82 int
  83 main(int argc, char **argv)
  84 {
  85         int c;
  86         int errors;
  87         int pflag = 0;
  88 
  89         (void) setlocale(LC_ALL, "");
  90 #if !defined(TEXT_DOMAIN)
  91 #define TEXT_DOMAIN "SYS_TEST"
  92 #endif
  93         (void) textdomain(TEXT_DOMAIN);
  94 
  95 
  96         while ((c = getopt(argc, argv, "pw")) != EOF) {
  97                 switch (c) {
  98                 case 'p':
  99                         pflag = 1;
 100                         break;
 101 
 102                 case 'w':
 103                         /* turn off warning messages */
 104                         printWarnings = 0;
 105                         break;
 106 
 107                 default:
 108                         usage();
 109                 }
 110         }
 111 
 112         argv += optind;
 113 
 114         if (*argv == 0) {
 115                 usage();
 116                 /* NOTREACHED */
 117         }
 118 
 119         errors = 0;
 120         while (*argv) {
 121                 errors += checkpathname(*argv, pflag);
 122                 argv += 1;
 123         }
 124 
 125         return (errors);
 126 }
 127 
 128 /*
 129  * checkPathConf(const char *, int, long *)
 130  *
 131  * Calls pathconf(), and returns 1 if pathconf failed, zero
 132  * otherwise.  If pathconf() succeeded, then *valp contains the
 133  * value returned
 134  */
 135 static int
 136 checkPathConf(const char *path, int type, long *valp)
 137 {
 138         errno = 0;
 139         *valp = pathconf(path, type);
 140         if ((*valp == -1) && (errno != 0) && (errno != EACCES)) {
 141                 /*
 142                  * pathconf() is not supported on some mounted filesystems
 143                  * (e.g NFS mounts) and pathconf() is known to fail.
 144                  * So, we print a warning and use the POSIX default values.
 145                  */
 146                 if (type == _PC_PATH_MAX)
 147                         *valp = _POSIX_PATH_MAX;
 148                 else
 149                         *valp = _POSIX_NAME_MAX;
 150 
 151                 if (printWarnings) {
 152                         (void) fprintf(stderr, gettext(pathconfprob), path,
 153                                 type == _PC_PATH_MAX?"_PC_PATH_MAX" :
 154                                     "_PC_NAME_MAX", strerror(errno),
 155                                 type == _PC_PATH_MAX ? "PATH_MAX" : "NAME_MAX",
 156                                     *valp);
 157                 }
 158         }
 159         return ((*valp == -1) && (errno != 0));
 160 }
 161 
 162 
 163 #define UPDATE_LIMITS(buf)\
 164 {\
 165         if (pflag) {\
 166                 nameMax = _POSIX_NAME_MAX;\
 167                 pathMax = _POSIX_PATH_MAX;\
 168         } else if (checkPathConf((buf), _PC_PATH_MAX, &pathMax) || \
 169             checkPathConf((buf), _PC_NAME_MAX, &nameMax)) {\
 170                 (void) fprintf(stderr, gettext(notsrch), buf);\
 171                 return (1);\
 172         }\
 173 }
 174 
 175 /*
 176  * checkpathname(char *pname)
 177  * pathchk a single pathname.
 178  */
 179 int
 180 checkpathname(char *path, int pflag)
 181 {
 182         int             checkStat;
 183         long            nameMax;
 184         long            pathMax;
 185         char            *scomp;
 186         char            *ecomp;
 187         register char   *p;
 188 
 189         p = path;
 190         checkStat = 1;
 191 
 192         /*
 193          * Get the initial NAME_MAX and PATH_MAX values
 194          */
 195         if (M_FSDELIM(*p)) {
 196                 char buf[2];
 197 
 198                 buf[0] = *p;
 199                 buf[1] = '\0';
 200 
 201                 UPDATE_LIMITS(buf);
 202         } else {
 203                 /*
 204                  * This is a relative pathname, initial values
 205                  * are relative to the current directory
 206                  */
 207                 UPDATE_LIMITS(".");
 208         }
 209 
 210         /*
 211          * Check to make sure that the pathname doesn't exceed the
 212          * current PATH_MAX
 213          */
 214         if (pathMax != -1 && strlen(p) > (size_t)pathMax) {
 215                 (void) fprintf(stderr, gettext(pathtoolong), path);
 216                 return (1);
 217         }
 218 
 219 
 220         /*
 221          * Now spin around checking all the prefixes of
 222          * the pathname, until we hit the end of the
 223          * argument
 224          */
 225         while (*p != '\0') {
 226                 /*
 227                  * Find the beginning of the next
 228                  * component.  Assume that
 229                  * M_FSDELIM('\0') == 0
 230                  */
 231                 while (M_FSDELIM(*p))
 232                         p += 1;
 233 
 234                 if (*p == '\0') {
 235                         /*
 236                          * There were trailing fsdelim chars on
 237                          * the path provided, so we were
 238                          * finished, we just didn't know it.
 239                          */
 240                         return (0);
 241                 }
 242 
 243                 scomp = p;
 244 
 245                 /*
 246                  * Find the end of the current component
 247                  * and check for valid characters in the component
 248                  */
 249                 while (*p != '\0' && !M_FSDELIM(*p)) {
 250                         /*
 251                          * for pflag: check for PFCS characters
 252                          * otherwise assume all characters are valid
 253                          */
 254                         if (pflag && (strchr(portfsset, *p) == 0)) {
 255                                 if (isprint(*p)) {
 256                                         (void) fprintf(stderr,
 257                                             gettext(badchar), path, *p, *p);
 258                                 } else {
 259                                         (void) fprintf(stderr,
 260                                             gettext(badbyte), path, *p);
 261                                 }
 262                                 return (1);
 263                         }
 264                         p += 1;
 265                  }
 266 
 267                 ecomp = p;
 268 
 269                 /*
 270                  * Make sure that this component does not exceed
 271                  * NAME_MAX in the current prefix directory
 272                  */
 273                 if ((nameMax != -1) && (ecomp - scomp > nameMax)) {
 274                         (void) fprintf(stderr, gettext(nametoolong), scomp);
 275                         return (1);
 276                 } else if (!pflag && checkStat) {
 277                         /*
 278                          * Perform the extra checks that
 279                          * are required when not just
 280                          * checking for portability.
 281                          */
 282                         struct stat sb;
 283                         char fsdelim;
 284 
 285                         fsdelim = *ecomp;
 286                         *ecomp = '\0';
 287 
 288                         if (stat(path, &sb) == -1) {
 289                                 /*
 290                                  * We error out if an
 291                                  * intermediate component
 292                                  * is a file, when we
 293                                  * were expecting a
 294                                  * directory, or it is an
 295                                  * unsearchable directory.
 296                                  */
 297                                 if ((errno == ENOTDIR && fsdelim != '\0') ||
 298                                     (errno == EACCES)) {
 299                                         (void) fprintf(stderr, gettext(notsrch),
 300                                                 path);
 301                                         return (1);
 302                                 } else if (errno == ENOENT) {
 303                                         checkStat = 0;
 304                                 }
 305                         } else if (S_ISDIR(sb.st_mode)) {
 306                                 /*
 307                                  * If the current prefix is a
 308                                  * directory, then we need to
 309                                  * update the limits for NAME_MAX
 310                                  * for the next component and the suffix.
 311                                  */
 312                                 if (checkPathConf(path, _PC_NAME_MAX,
 313                                     &nameMax)) {
 314                                         (void) fprintf(stderr,
 315                                             gettext(notsrch), path);
 316                                         return (1);
 317                                 }
 318                         }
 319 
 320                         /*
 321                          * restore the fsdelim char that we
 322                          * stomped to produce a prefix.
 323                          */
 324                         *ecomp = fsdelim;
 325                 } /* if (we need to stat the path) */
 326         } /* while (more of this path to check) */
 327 
 328         /*
 329          * We successfully traversed the whole pathname
 330          */
 331         return (0);
 332 }
 333 
 334 void
 335 usage()
 336 {
 337         (void) fprintf(stderr, gettext("usage: pathchk [-p] pathname ..."));
 338         (void) fprintf(stderr, "\n");
 339         exit(2);
 340 }