Path: seismo!harvard!husc6!panda!sources-request From: sources-request@panda.UUCP Newsgroups: mod.sources Subject: Improved and expanded chown/chgrp Message-ID: <1835@panda.UUCP> Date: 8 May 86 21:33:19 GMT Sender: jpn@panda.UUCP Lines: 238 Approved: jpn@panda.UUCP Mod.sources: Volume 4, Issue 107 Submitted by: condor!ihnp4!rosevax!rose3!merlyn!root [ I have not tried to verify that this program does not open any protection holes - I assume that it is intended to be installed as setuid to root since most (some?) UNIX'es will not let you give files away unless you are root. - John Nelson, moderator ] /* * chown, chgrp, chto, chusr, chmine, chsame - improved chown/chgrp * * This is a more flexible version of the chown/chgrp commands. * If REASONABLE is not #defined, chown and chgrp should be identical * to the originals (except for slight differences in the usage: * error message). Otherwise, two subtle changes will occur: * suid and sgid bits will be preserved for non-superusers (if possible), * and 'changing' a file to be what it already is (like "chown root /") * will always succeed. These slight changes were made to prevent * accidentally turning off suid/guid bits with global commands like * "chmine -s *" for non-superusers. Of course, bozoid stuff like * "chown root myprog" will still disable any suid bit (since the * chmod call to restore the suid bit will fail). * * Since all the programs are linked, the total size is about 40% * smaller than the original (separate) chown & chgrp. * * The six flavors are: * * chown [-s] owner files ... * chgrp [-s] group files ... * chto [-s] owner group files ... * chusr [-s] username files ... * chmine [-s] files ... * chsame [-s] template files ... * * chown & chgrp act as they always have. chto changes both owner * and group of the files (MUST be in this order, since "chto 1 2 foo" * would otherwise be ambiguous). chusr changes the owner & group to * match the given user's login uid and gid (chusr must be given a login * name, not a number). chmine changes the files to match your current * uid and gid. chsame changes the files to match the first file given, * as in "chsame . *" * * The -s option will not change the uid [gid] of files that have * the suid [sgid] bit set. This will avoid problems with: * cd /bin * chusr -s bin * * ...which would disable su, ps, etc. without the -s option. * * Flames to: ...ihnp4!umn-cs!rosevax!rose3!merlyn!brian (or root) * a.k.a Merlyn Leroy (back on the air!) * */ #include #include /* for isdigit */ #include /* for getpwnam */ #include /* for getgrnam */ #include /* for stat */ #include /* for stat */ /* #define REASONABLE if you want a more reasonable chown/grp/etc */ /* don't define it if you want slavish compatability with the old chown/grp */ #define REASONABLE #define when break;case /* for convenience */ #define otherwise break;default #define errchk(eval) if (eval) { perror(*argv); exitval = 1; continue; } #define SET_UID 1 #define SET_GID 2 #define SET_ALL (SET_UID | SET_GID) #define OWN 0 #define GRP 1 #define TO 2 #define USR 3 #define MINE 4 #define SAME 5 #define FIRST OWN /* FIRST is also the default function */ #define LAST SAME /* if invoked under an unknown name */ extern chown(); extern chmod(); extern stat(); extern struct passwd *getpwnam(); extern struct group *getgrnam(); struct passwd *pw; /* struct returned by getpwnam() */ struct group *gr; /* struct returned by getgrnam() */ char *progname; /* what is my name? */ short ami; /* what am i? (set to OWN, GRP, TO, etc) */ struct { char *prog; /* list of recognized program names */ short n,set; /* minimum # of params needed; id's to set */ } what[] = {{ "chown", 2, SET_UID}, { "chgrp", 2, SET_GID}, { "chto", 3, SET_ALL}, { "chusr", 2, SET_ALL}, { "chmine",1, SET_ALL}, { "chsame",2, SET_ALL}}; /* to avoid the silly strrchr vs. rindex problem, here is strrchr */ char *strrchr(s,ch) register char *s,ch; { register char *p; p = NULL; do { if (*s == ch) p = s; } while (*s++); return (p); } main(argc,argv) int argc; register char *argv[]; { struct stat status; /* to hold the stat() structure returned */ int uid,gid; /* uid and gid wanted */ int chuid,chgid; /* uid and gid to change */ short nargs,toset; /* minimum # of params; id's to set */ char saveset; /* boolean option */ int exitval; /* return value */ if ((progname = strrchr(*argv,'/')) != NULL) progname++; else progname = *argv; /* find my name */ what[FIRST].prog = progname; /* assign as default */ ami = LAST+1; while (strcmp(progname,what[--ami].prog)); /* find name in list */ nargs = what[ami].n; toset = what[ami].set; if (saveset = !strcmp(*++argv,"-s")) argv++, argc--; if (argc <= nargs) { fprintf(stderr,"usage: %s [-s] ",progname); switch (ami) { when USR: fprintf(stderr,"username "); when SAME: fprintf(stderr,"template "); when MINE: /* no parameters */ otherwise: if (toset & SET_UID) fprintf(stderr,"owner "); if (toset & SET_GID) fprintf(stderr,"group "); } fprintf(stderr,"files ...\n"); exit(4); } switch (ami) { when OWN: uid = finduid(*argv++); when GRP: gid = findgid(*argv++); when TO: uid = finduid(*argv++); gid = findgid(*argv++); when USR: uid = finduid(*argv++); gid = pw->pw_gid; /* from getpwnam */ when MINE: uid = getuid(); gid = getgid(); when SAME: if (stat(*argv,&status)) { perror(*argv); exit(4); } uid = status.st_uid; gid = status.st_gid; argv++; } exitval = 0; for (argc -= nargs; argc--; argv++) { errchk(stat(*argv,&status)); /* stat the file */ switch (toset) { when SET_UID: gid = status.st_gid; /* don't change gid */ when SET_GID: uid = status.st_uid; /* don't change uid */ } chuid = (saveset && (status.st_mode & S_ISUID)) ? status.st_uid : uid; chgid = (saveset && (status.st_mode & S_ISGID)) ? status.st_gid : gid; #ifdef REASONABLE /* * Only change files that need it, to be nice to non-superusers. * Otherwise, suid & sgid bits may be removed accidentally. */ if (chuid != status.st_uid || chgid != status.st_gid) { errchk(chown(*argv,chuid,chgid)); if (status.st_mode & (S_ISUID | S_ISGID)) chmod(*argv,status.st_mode); /* try to preserve suid & sgid */ } #else errchk(chown(*argv,chuid,chgid)); #endif /* REASONABLE */ } return (exitval); } /* find uid of user, or literal uid value */ /* chusr must find username only */ finduid(name) char *name; { if ((pw = getpwnam(name)) == NULL) if (nondigit(name)) { fprintf(stderr,"%s: unknown user id %s\n",progname,name); exit(4); } else if (ami == USR) { fprintf(stderr, "usage: %s [-s] username (NOT uid number) files ...\n",progname); exit(4); } else return (atoi(name)); else return (pw->pw_uid); } /* find gid of group, or literal gid value */ findgid(name) char *name; { if ((gr = getgrnam(name)) == NULL) if (nondigit(name)) { fprintf(stderr,"%s: unknown group id %s\n",progname,name); exit(4); } else return (atoi(name)); else return (gr->gr_gid); } /* return true (nonzero) if string contains a non-digit */ nondigit(s) register char *s; { while (*s && isdigit(*s)) s++; return *s; }