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