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	<stdio.h>
#include	<ctype.h>	/* for isdigit */
#include	<pwd.h>		/* for getpwnam */
#include	<grp.h>		/* for getgrnam */
#include	<sys/types.h>	/* for stat */
#include	<sys/stat.h>	/* 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;
}
