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 (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
  27 /*        All Rights Reserved   */
  28 
  29 /*
  30  * Portions of this source code were derived from Berkeley 4.3 BSD
  31  * under license from the Regents of the University of California.
  32  */
  33 
  34 /*
  35  * Copyright (c) 2018, Joyent, Inc.
  36  */
  37 
  38 /*
  39  * chown [-fhR] uid[:gid] file ...
  40  * chown -R [-f] [-H|-L|-P] uid[:gid] file ...
  41  * chown -s [-fhR] ownersid[:groupsid] file ...
  42  * chown -s -R [-f] [-H|-L|-P] ownersid[:groupsid] file ...
  43  */
  44 
  45 #include <stdio.h>
  46 #include <stdlib.h>
  47 #include <ctype.h>
  48 #include <sys/types.h>
  49 #include <dirent.h>
  50 #include <string.h>
  51 #include <sys/stat.h>
  52 #include <sys/avl.h>
  53 #include <pwd.h>
  54 #include <grp.h>
  55 #include <unistd.h>
  56 #include <locale.h>
  57 #include <errno.h>
  58 #include <libcmdutils.h>
  59 #include <aclutils.h>
  60 
  61 static struct           passwd  *pwd;
  62 static struct           group   *grp;
  63 static struct           stat    stbuf;
  64 static uid_t            uid = (uid_t)-1;
  65 static gid_t            gid = (gid_t)-1;
  66 static int              status = 0;     /* total number of errors received */
  67 static int              hflag = 0,
  68                         rflag = 0,
  69                         fflag = 0,
  70                         Hflag = 0,
  71                         Lflag = 0,
  72                         Pflag = 0,
  73                         sflag = 0;
  74 static avl_tree_t       *tree;
  75 
  76 static int              Perror(char *);
  77 static int              isnumber(char *);
  78 static void             chownr(char *, uid_t, gid_t);
  79 static void             usage();
  80 
  81 #ifdef XPG4
  82 /*
  83  * Check to see if we are to follow symlinks specified on the command line.
  84  * This assumes we've already checked to make sure neither -h or -P was
  85  * specified, so we are just looking to see if -R -H, or -R -L was specified,
  86  * or, since -R has the same behavior as -R -L, if -R was specified by itself.
  87  * Therefore, all we really need to check for is if -R was specified.
  88  */
  89 #define FOLLOW_CL_LINKS (rflag)
  90 #else
  91 /*
  92  * Check to see if we are to follow symlinks specified on the command line.
  93  * This assumes we've already checked to make sure neither -h or -P was
  94  * specified, so we are just looking to see if -R -H, or -R -L was specified.
  95  * Note: -R by itself will change the ownership of a directory referenced by a
  96  * symlink however it will now follow the symlink to any other part of the
  97  * file hierarchy.
  98  */
  99 #define FOLLOW_CL_LINKS (rflag && (Hflag || Lflag))
 100 #endif
 101 
 102 #ifdef XPG4
 103 /*
 104  * Follow symlinks when traversing directories.  Since -R behaves the
 105  * same as -R -L, we always want to follow symlinks to other parts
 106  * of the file hierarchy unless -H was specified.
 107  */
 108 #define FOLLOW_D_LINKS  (!Hflag)
 109 #else
 110 /*
 111  * Follow symlinks when traversing directories.  Only follow symlinks
 112  * to other parts of the file hierarchy if -L was specified.
 113  */
 114 #define FOLLOW_D_LINKS  (Lflag)
 115 #endif
 116 
 117 #define CHOWN(f, u, g)  if (chown(f, u, g) < 0) { \
 118                                 status += Perror(f); \
 119                         }
 120 #define LCHOWN(f, u, g) if (lchown(f, u, g) < 0) { \
 121                                 status += Perror(f); \
 122                         }
 123 
 124 
 125 int
 126 main(int argc, char *argv[])
 127 {
 128         int c;
 129         int ch;
 130         char *grpp;                     /* pointer to group name arg */
 131         extern int optind;
 132         int errflg = 0;
 133 
 134         (void) setlocale(LC_ALL, "");
 135 #if !defined(TEXT_DOMAIN)               /* Should be defined by cc -D */
 136 #define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it weren't */
 137 #endif
 138         (void) textdomain(TEXT_DOMAIN);
 139 
 140         while ((ch = getopt(argc, argv, "hRfHLPs")) != EOF) {
 141                 switch (ch) {
 142                 case 'h':
 143                         hflag++;
 144                         break;
 145 
 146                 case 'R':
 147                         rflag++;
 148                         break;
 149 
 150                 case 'f':
 151                         fflag++;
 152                         break;
 153 
 154                 case 'H':
 155                         /*
 156                          * If more than one of -H, -L, and -P
 157                          * are specified, only the last option
 158                          * specified determines the behavior of
 159                          * chown.
 160                          */
 161                         Lflag = Pflag = 0;
 162                         Hflag++;
 163                         break;
 164 
 165                 case 'L':
 166                         Hflag = Pflag = 0;
 167                         Lflag++;
 168                         break;
 169 
 170                 case 'P':
 171                         Hflag = Lflag = 0;
 172                         Pflag++;
 173                         break;
 174 
 175                 case 's':
 176                         sflag++;
 177                         break;
 178 
 179                 default:
 180                         errflg++;
 181                         break;
 182                 }
 183         }
 184         /*
 185          * Check for sufficient arguments
 186          * or a usage error.
 187          */
 188 
 189         argc -= optind;
 190         argv = &argv[optind];
 191 
 192         if (errflg || (argc < 2) ||
 193             ((Hflag || Lflag || Pflag) && !rflag) ||
 194             ((Hflag || Lflag || Pflag) && hflag)) {
 195                 usage();
 196         }
 197 
 198         /*
 199          * POSIX.2
 200          * Check for owner[:group]
 201          */
 202         if ((grpp = strchr(argv[0], ':')) != NULL) {
 203                 *grpp++ = 0;
 204 
 205                 if (sflag) {
 206                         if (sid_to_id(grpp, B_FALSE, &gid)) {
 207                                 (void) fprintf(stderr, gettext(
 208                                     "chown: invalid owning group sid %s\n"),
 209                                     grpp);
 210                                 exit(2);
 211                         }
 212                 } else if ((grp = getgrnam(grpp)) != NULL) {
 213                         gid = grp->gr_gid;
 214                 } else {
 215                         if (isnumber(grpp)) {
 216                                 errno = 0;
 217                                 gid = (gid_t)strtoul(grpp, NULL, 10);
 218                                 if (errno != 0) {
 219                                         if (errno == ERANGE) {
 220                                                 (void) fprintf(stderr, gettext(
 221                                                 "chown: group id too large\n"));
 222                                                 exit(2);
 223                                         } else {
 224                                                 (void) fprintf(stderr, gettext(
 225                                                 "chown: invalid group id\n"));
 226                                                 exit(2);
 227                                         }
 228                                 }
 229                         } else {
 230                                 (void) fprintf(stderr, gettext(
 231                                     "chown: unknown group id %s\n"), grpp);
 232                                 exit(2);
 233                         }
 234                 }
 235         }
 236 
 237         if (sflag) {
 238                 if (sid_to_id(argv[0], B_TRUE, &uid)) {
 239                         (void) fprintf(stderr, gettext(
 240                             "chown: invalid owner sid %s\n"), argv[0]);
 241                         exit(2);
 242                 }
 243         } else if ((pwd = getpwnam(argv[0])) != NULL) {
 244                 uid = pwd->pw_uid;
 245         } else {
 246                 if (isnumber(argv[0])) {
 247                         errno = 0;
 248                         uid = (uid_t)strtoul(argv[0], NULL, 10);
 249                         if (errno != 0) {
 250                                 if (errno == ERANGE) {
 251                                         (void) fprintf(stderr, gettext(
 252                                         "chown: user id too large\n"));
 253                                         exit(2);
 254                                 } else {
 255                                         (void) fprintf(stderr, gettext(
 256                                         "chown: invalid user id\n"));
 257                                         exit(2);
 258                                 }
 259                         }
 260                 } else {
 261                         (void) fprintf(stderr, gettext(
 262                         "chown: unknown user id %s\n"), argv[0]);
 263                         exit(2);
 264                 }
 265         }
 266 
 267         for (c = 1; c < argc; c++) {
 268                 tree = NULL;
 269                 if (lstat(argv[c], &stbuf) < 0) {
 270                         status += Perror(argv[c]);
 271                         continue;
 272                 }
 273                 if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFLNK)) {
 274                         if (hflag || Pflag) {
 275                                 /*
 276                                  * Change the ownership of the symlink
 277                                  * specified on the command line.
 278                                  * Don't follow the symbolic link to
 279                                  * any other part of the file hierarchy.
 280                                  */
 281                                 LCHOWN(argv[c], uid, gid);
 282                         } else {
 283                                 struct stat stbuf2;
 284                                 if (stat(argv[c], &stbuf2) < 0) {
 285                                         status += Perror(argv[c]);
 286                                         continue;
 287                                 }
 288                                 /*
 289                                  * We know that we are to change the
 290                                  * ownership of the file referenced by the
 291                                  * symlink specified on the command line.
 292                                  * Now check to see if we are to follow
 293                                  * the symlink to any other part of the
 294                                  * file hierarchy.
 295                                  */
 296                                 if (FOLLOW_CL_LINKS) {
 297                                         if ((stbuf2.st_mode & S_IFMT)
 298                                             == S_IFDIR) {
 299                                                 /*
 300                                                  * We are following symlinks so
 301                                                  * traverse into the directory.
 302                                                  * Add this node to the search
 303                                                  * tree so we don't get into an
 304                                                  * endless loop.
 305                                                  */
 306                                                 if (add_tnode(&tree,
 307                                                     stbuf2.st_dev,
 308                                                     stbuf2.st_ino) == 1) {
 309                                                         chownr(argv[c],
 310                                                             uid, gid);
 311                                                 } else {
 312                                                         /*
 313                                                          * Error occurred.
 314                                                          * rc can't be 0
 315                                                          * as this is the first
 316                                                          * node to be added to
 317                                                          * the search tree.
 318                                                          */
 319                                                         status += Perror(
 320                                                             argv[c]);
 321                                                 }
 322                                         } else {
 323                                                 /*
 324                                                  * Change the user ID of the
 325                                                  * file referenced by the
 326                                                  * symlink.
 327                                                  */
 328                                                 CHOWN(argv[c], uid, gid);
 329                                         }
 330                                 } else {
 331                                         /*
 332                                          * Change the user ID of the file
 333                                          * referenced by the symbolic link.
 334                                          */
 335                                         CHOWN(argv[c], uid, gid);
 336                                 }
 337                         }
 338                 } else if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) {
 339                         /*
 340                          * Add this node to the search tree so we don't
 341                          * get into a endless loop.
 342                          */
 343                         if (add_tnode(&tree, stbuf.st_dev,
 344                             stbuf.st_ino) == 1) {
 345                                 chownr(argv[c], uid, gid);
 346                         } else {
 347                                 /*
 348                                  * An error occurred while trying
 349                                  * to add the node to the tree.
 350                                  * Continue on with next file
 351                                  * specified.  Note: rc shouldn't
 352                                  * be 0 as this was the first node
 353                                  * being added to the search tree.
 354                                  */
 355                                 status += Perror(argv[c]);
 356                         }
 357                 } else if (hflag || Pflag) {
 358                         LCHOWN(argv[c], uid, gid);
 359                 } else {
 360                         CHOWN(argv[c], uid, gid);
 361                 }
 362         }
 363         return (status);
 364 }
 365 
 366 /*
 367  * chownr() - recursive chown()
 368  *
 369  * Recursively chowns the input directory then its contents.  rflag must
 370  * have been set if chownr() is called.  The input directory should not
 371  * be a sym link (this is handled in the calling routine).  In
 372  * addition, the calling routine should have already added the input
 373  * directory to the search tree so we do not get into endless loops.
 374  * Note: chownr() doesn't need a return value as errors are reported
 375  * through the global "status" variable.
 376  */
 377 static void
 378 chownr(char *dir, uid_t uid, gid_t gid)
 379 {
 380         DIR *dirp;
 381         struct dirent *dp;
 382         struct stat st, st2;
 383         char savedir[1024];
 384 
 385         if (getcwd(savedir, 1024) == (char *)0) {
 386                 (void) Perror("getcwd");
 387                 exit(255);
 388         }
 389 
 390         /*
 391          * Attempt to chown the directory, however don't return if we
 392          * can't as we still may be able to chown the contents of the
 393          * directory.  Note: the calling routine resets the SUID bits
 394          * on this directory so we don't have to perform an extra 'stat'.
 395          */
 396         CHOWN(dir, uid, gid);
 397 
 398         if (chdir(dir) < 0) {
 399                 status += Perror(dir);
 400                 return;
 401         }
 402         if ((dirp = opendir(".")) == NULL) {
 403                 status += Perror(dir);
 404                 return;
 405         }
 406         for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
 407                 if (strcmp(dp->d_name, ".") == 0 ||  /* skip . and .. */
 408                     strcmp(dp->d_name, "..") == 0) {
 409                         continue;
 410                 }
 411                 if (lstat(dp->d_name, &st) < 0) {
 412                         status += Perror(dp->d_name);
 413                         continue;
 414                 }
 415                 if ((st.st_mode & S_IFMT) == S_IFLNK) {
 416                         if (hflag || Pflag) {
 417                                 /*
 418                                  * Change the ownership of the symbolic link
 419                                  * encountered while traversing the
 420                                  * directory.  Don't follow the symbolic
 421                                  * link to any other part of the file
 422                                  * hierarchy.
 423                                  */
 424                                 LCHOWN(dp->d_name, uid, gid);
 425                         } else {
 426                                 if (stat(dp->d_name, &st2) < 0) {
 427                                         status += Perror(dp->d_name);
 428                                         continue;
 429                                 }
 430                                 /*
 431                                  * We know that we are to change the
 432                                  * ownership of the file referenced by the
 433                                  * symlink encountered while traversing
 434                                  * the directory.  Now check to see if we
 435                                  * are to follow the symlink to any other
 436                                  * part of the file hierarchy.
 437                                  */
 438                                 if (FOLLOW_D_LINKS) {
 439                                         if ((st2.st_mode & S_IFMT) == S_IFDIR) {
 440                                                 /*
 441                                                  * We are following symlinks so
 442                                                  * traverse into the directory.
 443                                                  * Add this node to the search
 444                                                  * tree so we don't get into an
 445                                                  * endless loop.
 446                                                  */
 447                                                 int rc;
 448                                                 if ((rc = add_tnode(&tree,
 449                                                     st2.st_dev,
 450                                                     st2.st_ino)) == 1) {
 451                                                         chownr(dp->d_name,
 452                                                             uid, gid);
 453                                                 } else if (rc == 0) {
 454                                                         /* already visited */
 455                                                         continue;
 456                                                 } else {
 457                                                         /*
 458                                                          * An error occurred
 459                                                          * while trying to add
 460                                                          * the node to the tree.
 461                                                          */
 462                                                         status += Perror(
 463                                                             dp->d_name);
 464                                                         continue;
 465                                                 }
 466                                         } else {
 467                                                 /*
 468                                                  * Change the user id of the
 469                                                  * file referenced by the
 470                                                  * symbolic link.
 471                                                  */
 472                                                 CHOWN(dp->d_name, uid, gid);
 473                                         }
 474                                 } else {
 475                                         /*
 476                                          * Change the user id of the file
 477                                          * referenced by the symbolic link.
 478                                          */
 479                                         CHOWN(dp->d_name, uid, gid);
 480                                 }
 481                         }
 482                 } else if ((st.st_mode & S_IFMT) == S_IFDIR) {
 483                         /*
 484                          * Add this node to the search tree so we don't
 485                          * get into a endless loop.
 486                          */
 487                         int rc;
 488                         if ((rc = add_tnode(&tree, st.st_dev,
 489                             st.st_ino)) == 1) {
 490                                 chownr(dp->d_name, uid, gid);
 491                         } else if (rc == 0) {
 492                                 /* already visited */
 493                                 continue;
 494                         } else {
 495                                 /*
 496                                  * An error occurred while trying
 497                                  * to add the node to the search tree.
 498                                  */
 499                                 status += Perror(dp->d_name);
 500                                 continue;
 501                         }
 502                 } else {
 503                         CHOWN(dp->d_name, uid, gid);
 504                 }
 505         }
 506 
 507         (void) closedir(dirp);
 508         if (chdir(savedir) < 0) {
 509                 (void) fprintf(stderr, gettext(
 510                     "chown: can't change back to %s\n"), savedir);
 511                 exit(255);
 512         }
 513 }
 514 
 515 static int
 516 isnumber(char *s)
 517 {
 518         int c;
 519 
 520         while ((c = *s++) != '\0')
 521                 if (!isdigit(c))
 522                         return (0);
 523         return (1);
 524 }
 525 
 526 static int
 527 Perror(char *s)
 528 {
 529         if (!fflag) {
 530                 (void) fprintf(stderr, "chown: ");
 531                 perror(s);
 532         }
 533         return (!fflag);
 534 }
 535 
 536 static void
 537 usage()
 538 {
 539         (void) fprintf(stderr, gettext(
 540             "usage:\n"
 541             "\tchown [-fhR] owner[:group] file...\n"
 542             "\tchown -R [-f] [-H|-L|-P] owner[:group] file...\n"
 543             "\tchown -s [-fhR] ownersid[:groupsid] file...\n"
 544             "\tchown -s -R [-f] [-H|-L|-P] ownersid[:groupsid] file...\n"));
 545         exit(2);
 546 }