/* The copyright holder disclaims all responsibility or liability with    */
/* respect to its usage or its effect upon hardware or computer           */
/* systems, and maintains copyright as set out in the "LICENSE"           */
/* document which accompanies distribution.                               */
/* Titan version 4.0.10 March 24 01:21:02 PDT 2001                        */
/*                                                                        */
/*                                                                        */
/* runas.c -- A tool to execute processes under a different UID, GID,     */
/*		and umask.                                                */ 
/*                                                                        */
/* $Id: runas.c,v 1.5 2001/03/24 12:21:27 Exp $                           */
/*                                                                        */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

extern int errno;

void usage(char *cmdName) {
    fprintf(stderr, "Usage: %s [-c dir] username/UID groupname/GID umask command [opts]\n",
    cmdName);
    fprintf(stderr, "    -c dir   chroot(2) to directory before execution\n");
}

int main(argc, argv, envp)
int     argc;
char  **argv;
char  **envp;
{

    /* option parsing (getopt) variables */
    extern char *optarg;
    extern int optind;
    short errFlag = 0;
    int c;

    char *cmdName = argv[0];
    struct passwd *pwEntry;
    struct group  *grEntry;
    mode_t uMask;
    uid_t newUID;
    gid_t newGID;
    char *chrootDir;
    short useChroot = 0;

    /* parse commmand line looking for "chroot" option */
    while ((c = getopt(argc, argv, "c:")) != EOF) {
	switch (c) {
	    case 'c':
		chrootDir = optarg;
		useChroot++;
		break;
	    case '?':
		errFlag++;
	}
    }
    if (errFlag) {
	usage(cmdName);
	return(1);
    } 

    /* advance pointer if argument was parsed */
    if (useChroot)
	argv += optind - 1;

    /* check the total number of arguments */
    if (argc < (optind - 1) + 5) {
	usage(cmdName);
	return(1);
    }

    /* check that we are running as root */
    if (geteuid() != 0) {
	fprintf(stderr, "%s: must be run as root.\n", cmdName);
	return(1);
    }

    /* null out all environment variables */
    envp[0] = NULL;

    /* eliminate all supplementary groups */
    if (setgroups(0, NULL) < 0) {
	fprintf(stderr, "%s: unable to eliminate supplementary groups: %s\n",
	    cmdName, strerror(errno));
	return(1);
    }

    /* parse GID argument; only allow a numeric argument */
    if (isdigit((int)argv[2][0]))
	newGID = atoi(argv[2]);
    else {
	if ((grEntry = getgrnam(argv[2])) == NULL) {
	    fprintf(stderr, "%s: Invalid group name: %s\n", cmdName, argv[2]);
	    return(1);
	}
	newGID = grEntry->gr_gid;
    }

    /* switch GID */
    if (setgid(newGID) < 0) {
	fprintf(stderr, "%s: unable to set group ID: %s\n", cmdName,
	    strerror(errno));
	return(1);
    }

    /* parse UID argument; only allow a numeric argument */
    if (isdigit((int)argv[1][0]))
	newUID = atoi(argv[1]);
    else {
	if ((pwEntry = getpwnam(argv[1])) == NULL) {
	    fprintf(stderr, "%s: Invalid user name: %s\n", cmdName, argv[1]);
	    return(1);
	}
	newUID = pwEntry->pw_uid;
    }
    
    /* check that we are not setting the UID to root (0) */
    if (newUID == 0) {
	fprintf(stderr, "%s: UID 0 (root) not allowed.\n", cmdName);
	return(1);
    }

    /* chroot to the new location */
    if (useChroot) {
	if (chroot(chrootDir) < 0) {
	    fprintf(stderr, "%s: unable to chroot to %s: %s\n", cmdName,
		chrootDir, strerror(errno));
	    return(1);
	}
    }

    /* switch UID; this must be done last or eliminating supplementary
	groups and switching GIDs will not work.
     */
    if (setuid(newUID) < 0) {
	fprintf(stderr, "%s: unable to set user ID: %s\n", cmdName,
	    strerror(errno));
	return(1);
    }

    /* set umask */
    if (isdigit((int)argv[3][0])) {
	int ccnt;
	uint readMask;
	ccnt = sscanf(argv[3], "%o", &readMask);
	if (ccnt != 1) {
	    fprintf(stderr, "%s: umask '%s' is not an octal value\n",
		cmdName, argv[3]);
	    return(1);
	}
	uMask = (mode_t)readMask;

    } else {
	fprintf(stderr, "%s: umask '%s' is not an octal value\n", cmdName,
	    argv[3]);
	return(1);
    }

    /* set the umask */
    (void) umask(uMask);

    /* execute the command */
    execvp(argv[4], argv + 4);

    /* this point is only reached if execvp() fails */
    fprintf(stderr, "%s: unable to execute command '%s': %s\n", cmdName,
	argv[4], strerror(errno));
    return(1);
}

/*
 * $Log: runas.c,v $
 * Revision 1.5  2001/03/24 10:21:27 
 * Added chroot(2) capabilities. Also, created a function for the usage
 * message. Added quotes to some error messages to make the string that
 * caused the error to stand out.
 *
 */
