1 /* $OpenBSD: chmod.c,v 1.27 2011/03/09 12:32:02 okan 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/param.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 #ifdef lint 70 set = NULL; 71 oct = omode = 0; 72 #endif 73 74 setlocale(LC_ALL, ""); 75 76 ischown = __progname[2] == 'o'; 77 ischgrp = __progname[2] == 'g'; 78 ischmod = __progname[2] == 'm'; 79 ischflags = __progname[2] == 'f'; 80 81 uid = (uid_t)-1; 82 gid = (gid_t)-1; 83 Hflag = Lflag = Rflag = fflag = hflag = 0; 84 while ((ch = getopt(argc, argv, "HLPRXfghorstuwx")) != -1) 85 switch (ch) { 86 case 'H': 87 Hflag = 1; 88 Lflag = 0; 89 break; 90 case 'L': 91 Lflag = 1; 92 Hflag = 0; 93 break; 94 case 'P': 95 Hflag = Lflag = 0; 96 break; 97 case 'R': 98 Rflag = 1; 99 break; 100 case 'f': /* no longer documented. */ 101 fflag = 1; 102 break; 103 case 'h': 104 /* 105 * In System V (and probably POSIX.2) the -h option 106 * causes chmod to change the mode of the symbolic 107 * link. 4.4BSD's symbolic links don't have modes, 108 * so it's an undocumented noop. Do syntax checking, 109 * though. 110 */ 111 hflag = 1; 112 break; 113 /* 114 * XXX 115 * "-[rwx]" are valid mode commands. If they are the entire 116 * argument, getopt has moved past them, so decrement optind. 117 * Regardless, we're done argument processing. 118 */ 119 case 'g': case 'o': case 'r': case 's': 120 case 't': case 'u': case 'w': case 'X': case 'x': 121 if (!ischmod) 122 usage(); 123 if (argv[optind - 1][0] == '-' && 124 argv[optind - 1][1] == ch && 125 argv[optind - 1][2] == '\0') 126 --optind; 127 goto done; 128 default: 129 usage(); 130 } 131 done: 132 argv += optind; 133 argc -= optind; 134 135 if (argc < 2) 136 usage(); 137 138 fts_options = FTS_PHYSICAL; 139 if (Rflag) { 140 if (hflag) 141 errx(1, 142 "the -R and -h options may not be specified together."); 143 if (Hflag) 144 fts_options |= FTS_COMFOLLOW; 145 if (Lflag) { 146 fts_options &= ~FTS_PHYSICAL; 147 fts_options |= FTS_LOGICAL; 148 } 149 } 150 151 if (ischflags) { 152 flags = *argv; 153 if (*flags >= '0' && *flags <= '7') { 154 errno = 0; 155 val = strtoul(flags, &ep, 8); 156 if (val > UINT_MAX) 157 errno = ERANGE; 158 if (errno) 159 err(1, "invalid flags: %s", flags); 160 if (*ep) 161 errx(1, "invalid flags: %s", flags); 162 fset = val; 163 oct = 1; 164 } else { 165 if (strtofflags(&flags, &fset, &fclear)) 166 errx(1, "invalid flag: %s", flags); 167 fclear = ~fclear; 168 oct = 0; 169 } 170 } else if (ischmod) { 171 mode = *argv; 172 if (*mode >= '0' && *mode <= '7') { 173 errno = 0; 174 val = strtol(mode, &ep, 8); 175 if (val > INT_MAX || val < 0) 176 errno = ERANGE; 177 if (errno) 178 err(1, "invalid file mode: %s", mode); 179 if (*ep) 180 errx(1, "invalid file mode: %s", mode); 181 omode = val; 182 oct = 1; 183 } else { 184 if ((set = setmode(mode)) == NULL) 185 errx(1, "invalid file mode: %s", mode); 186 oct = 0; 187 } 188 } else if (ischown) { 189 /* Both UID and GID are given. */ 190 if ((cp = strchr(*argv, ':')) != NULL) { 191 *cp++ = '\0'; 192 gid = a_gid(cp); 193 } 194 #ifdef SUPPORT_DOT 195 /* UID and GID are separated by a dot and UID exists. */ 196 else if ((cp = strchr(*argv, '.')) != NULL && 197 (uid = a_uid(*argv, 1)) == (uid_t)-1) { 198 *cp++ = '\0'; 199 gid = a_gid(cp); 200 } 201 #endif 202 if (uid == (uid_t)-1) 203 uid = a_uid(*argv, 0); 204 } else 205 gid = a_gid(*argv); 206 207 if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL) 208 err(1, NULL); 209 for (rval = 0; (p = fts_read(ftsp)) != NULL;) { 210 switch (p->fts_info) { 211 case FTS_D: 212 if (!Rflag) 213 fts_set(ftsp, p, FTS_SKIP); 214 if (ischmod) 215 break; 216 else 217 continue; 218 case FTS_DNR: /* Warn, chmod, continue. */ 219 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 220 rval = 1; 221 break; 222 case FTS_DP: /* Already changed at FTS_D. */ 223 if (ischmod) 224 continue; 225 else 226 break; 227 case FTS_ERR: /* Warn, continue. */ 228 case FTS_NS: 229 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 230 rval = 1; 231 continue; 232 case FTS_SL: /* Ignore. */ 233 case FTS_SLNONE: 234 /* 235 * The only symlinks that end up here are ones that 236 * don't point to anything and ones that we found 237 * doing a physical walk. 238 */ 239 if (ischflags || ischmod || !hflag) 240 continue; 241 break; 242 default: 243 break; 244 } 245 if (ischflags) { 246 if (oct) { 247 if (!chflags(p->fts_accpath, fset)) 248 continue; 249 } else { 250 p->fts_statp->st_flags |= fset; 251 p->fts_statp->st_flags &= fclear; 252 if (!chflags(p->fts_accpath, p->fts_statp->st_flags)) 253 continue; 254 } 255 warn("%s", p->fts_path); 256 rval = 1; 257 } else if (ischmod && chmod(p->fts_accpath, oct ? omode : 258 getmode(set, p->fts_statp->st_mode)) && !fflag) { 259 warn("%s", p->fts_path); 260 rval = 1; 261 } else if (!ischmod && !ischflags && 262 (hflag ? lchown(p->fts_accpath, uid, gid) : 263 chown(p->fts_accpath, uid, gid)) && !fflag) { 264 warn("%s", p->fts_path); 265 rval = 1; 266 } 267 } 268 if (errno) 269 err(1, "fts_read"); 270 exit(rval); 271 } 272 273 /* 274 * Given a UID or user name in a string, return the UID. If an empty string 275 * was given, returns -1. If silent is 0, exits on invalid user names/UIDs; 276 * otherwise, returns -1. 277 */ 278 uid_t 279 a_uid(const char *s, int silent) 280 { 281 struct passwd *pw; 282 const char *errstr; 283 uid_t uid; 284 285 if (*s == '\0') /* Argument was "[:.]gid". */ 286 return ((uid_t)-1); 287 288 /* User name was given. */ 289 if ((pw = getpwnam(s)) != NULL) 290 return (pw->pw_uid); 291 292 /* UID was given. */ 293 uid = (uid_t)strtonum(s, 0, UID_MAX, &errstr); 294 if (errstr) { 295 if (silent) 296 return ((uid_t)-1); 297 else 298 errx(1, "user is %s: %s", errstr, s); 299 } 300 301 return (uid); 302 } 303 304 /* 305 * Given a GID or group name in a string, return the GID. If an empty string 306 * was given, returns -1. Exits on invalid user names/UIDs. 307 */ 308 gid_t 309 a_gid(const char *s) 310 { 311 struct group *gr; 312 const char *errstr; 313 gid_t gid; 314 315 if (*s == '\0') /* Argument was "uid[:.]". */ 316 return ((gid_t)-1); 317 318 /* Group name was given. */ 319 if ((gr = getgrnam(s)) != NULL) 320 return (gr->gr_gid); 321 322 /* GID was given. */ 323 gid = (gid_t)strtonum(s, 0, GID_MAX, &errstr); 324 if (errstr) 325 errx(1, "group is %s: %s", errstr, s); 326 327 return (gid); 328 } 329 330 void 331 usage(void) 332 { 333 if (ischmod || ischflags) 334 fprintf(stderr, 335 "usage: %s [-R [-H | -L | -P]] %s file ...\n", 336 __progname, ischmod ? "mode" : "flags"); 337 else 338 fprintf(stderr, 339 "usage: %s [-h] [-R [-H | -L | -P]] %s file ...\n", 340 __progname, ischown ? "owner[:group]" : "group"); 341 if (ischown) 342 fprintf(stderr, 343 " %s [-h] [-R [-H | -L | -P]] :group file ...\n", 344 __progname); 345 exit(1); 346 } 347