1 /* $OpenBSD: chmod.c,v 1.29 2014/03/16 18:38:30 guenther Exp $ */ 2 /* $NetBSD: chmod.c,v 1.12 1995/03/21 09:02:09 cgd Exp $ */ 3 4 /* 5 * Copyright (c) 1989, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/types.h> 34 #include <sys/stat.h> 35 36 #include <err.h> 37 #include <errno.h> 38 #include <fts.h> 39 #include <grp.h> 40 #include <limits.h> 41 #include <locale.h> 42 #include <pwd.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 int ischflags, ischown, ischgrp, ischmod; 49 extern char *__progname; 50 51 gid_t a_gid(const char *); 52 uid_t a_uid(const char *, int); 53 __dead void usage(void); 54 55 int 56 main(int argc, char *argv[]) 57 { 58 FTS *ftsp; 59 FTSENT *p; 60 void *set; 61 long val; 62 int oct; 63 mode_t omode; 64 int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval; 65 uid_t uid; 66 gid_t gid; 67 u_int32_t fclear, fset; 68 char *ep, *mode, *cp, *flags; 69 70 setlocale(LC_ALL, ""); 71 72 ischown = __progname[2] == 'o'; 73 ischgrp = __progname[2] == 'g'; 74 ischmod = __progname[2] == 'm'; 75 ischflags = __progname[2] == 'f'; 76 77 uid = (uid_t)-1; 78 gid = (gid_t)-1; 79 Hflag = Lflag = Rflag = fflag = hflag = 0; 80 while ((ch = getopt(argc, argv, "HLPRXfghorstuwx")) != -1) 81 switch (ch) { 82 case 'H': 83 Hflag = 1; 84 Lflag = 0; 85 break; 86 case 'L': 87 Lflag = 1; 88 Hflag = 0; 89 break; 90 case 'P': 91 Hflag = Lflag = 0; 92 break; 93 case 'R': 94 Rflag = 1; 95 break; 96 case 'f': /* no longer documented. */ 97 fflag = 1; 98 break; 99 case 'h': 100 /* 101 * In System V (and probably POSIX.2) the -h option 102 * causes chmod to change the mode of the symbolic 103 * link. 4.4BSD's symbolic links don't have modes, 104 * so it's an undocumented noop. Do syntax checking, 105 * though. 106 */ 107 hflag = 1; 108 break; 109 /* 110 * XXX 111 * "-[rwx]" are valid mode commands. If they are the entire 112 * argument, getopt has moved past them, so decrement optind. 113 * Regardless, we're done argument processing. 114 */ 115 case 'g': case 'o': case 'r': case 's': 116 case 't': case 'u': case 'w': case 'X': case 'x': 117 if (!ischmod) 118 usage(); 119 if (argv[optind - 1][0] == '-' && 120 argv[optind - 1][1] == ch && 121 argv[optind - 1][2] == '\0') 122 --optind; 123 goto done; 124 default: 125 usage(); 126 } 127 done: 128 argv += optind; 129 argc -= optind; 130 131 if (argc < 2) 132 usage(); 133 134 fts_options = FTS_PHYSICAL; 135 if (Rflag) { 136 if (hflag) 137 errx(1, 138 "the -R and -h options may not be specified together."); 139 if (Hflag) 140 fts_options |= FTS_COMFOLLOW; 141 if (Lflag) { 142 fts_options &= ~FTS_PHYSICAL; 143 fts_options |= FTS_LOGICAL; 144 } 145 } 146 147 if (ischflags) { 148 flags = *argv; 149 if (*flags >= '0' && *flags <= '7') { 150 errno = 0; 151 val = strtoul(flags, &ep, 8); 152 if (val > UINT_MAX) 153 errno = ERANGE; 154 if (errno) 155 err(1, "invalid flags: %s", flags); 156 if (*ep) 157 errx(1, "invalid flags: %s", flags); 158 fset = val; 159 oct = 1; 160 } else { 161 if (strtofflags(&flags, &fset, &fclear)) 162 errx(1, "invalid flag: %s", flags); 163 fclear = ~fclear; 164 oct = 0; 165 } 166 } else if (ischmod) { 167 mode = *argv; 168 if (*mode >= '0' && *mode <= '7') { 169 errno = 0; 170 val = strtol(mode, &ep, 8); 171 if (val > INT_MAX || val < 0) 172 errno = ERANGE; 173 if (errno) 174 err(1, "invalid file mode: %s", mode); 175 if (*ep) 176 errx(1, "invalid file mode: %s", mode); 177 omode = val; 178 oct = 1; 179 } else { 180 if ((set = setmode(mode)) == NULL) 181 errx(1, "invalid file mode: %s", mode); 182 oct = 0; 183 } 184 } else if (ischown) { 185 /* Both UID and GID are given. */ 186 if ((cp = strchr(*argv, ':')) != NULL) { 187 *cp++ = '\0'; 188 gid = a_gid(cp); 189 } 190 #ifdef SUPPORT_DOT 191 /* UID and GID are separated by a dot and UID exists. */ 192 else if ((cp = strchr(*argv, '.')) != NULL && 193 (uid = a_uid(*argv, 1)) == (uid_t)-1) { 194 *cp++ = '\0'; 195 gid = a_gid(cp); 196 } 197 #endif 198 if (uid == (uid_t)-1) 199 uid = a_uid(*argv, 0); 200 } else 201 gid = a_gid(*argv); 202 203 if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL) 204 err(1, NULL); 205 for (rval = 0; (p = fts_read(ftsp)) != NULL;) { 206 switch (p->fts_info) { 207 case FTS_D: 208 if (!Rflag) 209 fts_set(ftsp, p, FTS_SKIP); 210 if (ischmod) 211 break; 212 else 213 continue; 214 case FTS_DNR: /* Warn, chmod, continue. */ 215 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 216 rval = 1; 217 break; 218 case FTS_DP: /* Already changed at FTS_D. */ 219 if (ischmod) 220 continue; 221 else 222 break; 223 case FTS_ERR: /* Warn, continue. */ 224 case FTS_NS: 225 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 226 rval = 1; 227 continue; 228 case FTS_SL: /* Ignore. */ 229 case FTS_SLNONE: 230 /* 231 * The only symlinks that end up here are ones that 232 * don't point to anything and ones that we found 233 * doing a physical walk. 234 */ 235 if (ischflags || ischmod || !hflag) 236 continue; 237 break; 238 default: 239 break; 240 } 241 if (ischflags) { 242 if (oct) { 243 if (!chflags(p->fts_accpath, fset)) 244 continue; 245 } else { 246 p->fts_statp->st_flags |= fset; 247 p->fts_statp->st_flags &= fclear; 248 if (!chflags(p->fts_accpath, p->fts_statp->st_flags)) 249 continue; 250 } 251 warn("%s", p->fts_path); 252 rval = 1; 253 } else if (ischmod && chmod(p->fts_accpath, oct ? omode : 254 getmode(set, p->fts_statp->st_mode)) && !fflag) { 255 warn("%s", p->fts_path); 256 rval = 1; 257 } else if (!ischmod && !ischflags && 258 (hflag ? lchown(p->fts_accpath, uid, gid) : 259 chown(p->fts_accpath, uid, gid)) && !fflag) { 260 warn("%s", p->fts_path); 261 rval = 1; 262 } 263 } 264 if (errno) 265 err(1, "fts_read"); 266 exit(rval); 267 } 268 269 /* 270 * Given a UID or user name in a string, return the UID. If an empty string 271 * was given, returns -1. If silent is 0, exits on invalid user names/UIDs; 272 * otherwise, returns -1. 273 */ 274 uid_t 275 a_uid(const char *s, int silent) 276 { 277 struct passwd *pw; 278 const char *errstr; 279 uid_t uid; 280 281 if (*s == '\0') /* Argument was "[:.]gid". */ 282 return ((uid_t)-1); 283 284 /* User name was given. */ 285 if ((pw = getpwnam(s)) != NULL) 286 return (pw->pw_uid); 287 288 /* UID was given. */ 289 uid = (uid_t)strtonum(s, 0, UID_MAX, &errstr); 290 if (errstr) { 291 if (silent) 292 return ((uid_t)-1); 293 else 294 errx(1, "user is %s: %s", errstr, s); 295 } 296 297 return (uid); 298 } 299 300 /* 301 * Given a GID or group name in a string, return the GID. If an empty string 302 * was given, returns -1. Exits on invalid user names/UIDs. 303 */ 304 gid_t 305 a_gid(const char *s) 306 { 307 struct group *gr; 308 const char *errstr; 309 gid_t gid; 310 311 if (*s == '\0') /* Argument was "uid[:.]". */ 312 return ((gid_t)-1); 313 314 /* Group name was given. */ 315 if ((gr = getgrnam(s)) != NULL) 316 return (gr->gr_gid); 317 318 /* GID was given. */ 319 gid = (gid_t)strtonum(s, 0, GID_MAX, &errstr); 320 if (errstr) 321 errx(1, "group is %s: %s", errstr, s); 322 323 return (gid); 324 } 325 326 void 327 usage(void) 328 { 329 if (ischmod || ischflags) 330 fprintf(stderr, 331 "usage: %s [-R [-H | -L | -P]] %s file ...\n", 332 __progname, ischmod ? "mode" : "flags"); 333 else 334 fprintf(stderr, 335 "usage: %s [-h] [-R [-H | -L | -P]] %s file ...\n", 336 __progname, ischown ? "owner[:group]" : "group"); 337 if (ischown) 338 fprintf(stderr, 339 " %s [-h] [-R [-H | -L | -P]] :group file ...\n", 340 __progname); 341 exit(1); 342 } 343