/*
 * Copyright (c) 2003, 2004, 2006, 2007 Roger Seguin <roger_seguin@msn.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

static char     rcsid[] =
    "$Id: runas.c,v 1.8 2007/05/10 06:23:40 RogerSeguin Exp $"
    "\nCopyright (c) 2003, 2004, 2006, 2007 Roger Seguin <roger_seguin@msn.com,"
    " http://rmlx.dyndns.org>";

/*
Description:
	Execute a command with substitute user and group Id's
	Usage:	runas [-u user[:group]] cmd params...
	Program must be setuid'd if to be run by non-privileged  user
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>


/**********************************************************************/
static const char *Basename (const char *p_pcPath)
/**********************************************************************/
{
    char           *pc = rindex (p_pcPath, '/');
    return (pc ? pc + 1 : p_pcPath);
}


/**********************************************************************/
static int Usage (const char *const This, FILE * p_pFOut)
/**********************************************************************/
{
    FILE           *pFOut = p_pFOut;
    fprintf (stdout, "%s\n", rcsid + 5);
    fprintf (stdout,
	     "Execute a command with substitute user and group Id's\n");
    fprintf (pFOut, "Usage:\n");
    fprintf (pFOut, "\t%s [-u user[:group]] commandline...\n", This);
    fprintf (pFOut, "\t%s -h\n", This);
    fprintf (pFOut, "\t\t-u\tUser:\tset cmd's user/group Id.\n");
    fprintf (pFOut, "\t\t-h\tHelp:\toutput this help and exit\n");
    fprintf (pFOut, "Description:\n");
    fprintf (pFOut, "\tuser/group: name or id number of the user/group\n");
    return (0);
}


/**********************************************************************/
int main (int argc, char **argv)
/**********************************************************************/
{
    const char     *const pcThis = Basename (argv[0]);
    struct passwd  *poUsr;
    struct group   *poGrp;
    long int        uid = 0, gid = 0;	/* Default to root:root */
    int             fError = 0, fUsage = 0;
    char            c, *pcU = 0, *pcIds = 0, *pcId, *pcStr;
    char          **argv2;
    int             status;

#if UID
    // Program restricted to user indicated at compilation time
    if (getuid () != UID) {
	fprintf (stderr, "%s: must be run as user %d.\n", pcThis, UID);
	return (~0);
    }
#endif

    /* Parse command line options */
    putenv ("POSIXLY_CORRECT=1");
    for (; !fUsage && !fError && (c = getopt (argc, argv, "hu:")) != EOF;)
	switch (c) {
	    case 'u':		/* User/Group */
		pcU = optarg;
		break;
	    case 'h':		/* Help */
		fUsage = 1;
		break;
	    case '?':
	    default:
		fError = 1;
	}
    if (!fUsage)
	fError |= (argc < (optind + 1));	/* username + command */
    fUsage |= fError;

    /* Display usage message whether requested or error detected */
    if (fUsage) {
	Usage (pcThis, fError ? stderr : stdout);
	return (fError ? ~0 : 0);
    }

    if (pcU) {			/* command line includes -u option */

	/* Make a working copy of User/Group Id's string */
	pcIds = strdup (pcU);
	if (!pcIds) {
	    perror (pcU);
	    goto Error;
	}

	/* Get group id number from userId:groupId */
	pcId = strstr (pcIds, ":");
	if (pcId) {
	    *pcId++ = 0;	/* Break pcIds into 2 parameters */
	    gid = strtol (pcId, &pcStr, 10);
	    if (*pcStr) {
		/* Not a numeric value - Must be the group name */
		poGrp = getgrnam (pcStr);
		if (!poGrp) {
		    fprintf (stderr, "Error: %s - wrong groupId\n", pcId);
		    goto Error;
		}
		gid = poGrp->gr_gid;
	    }
	}

	/* Get user id number */
	pcId = pcIds;
	uid = strtol (pcId, &pcStr, 10);
	if (*pcStr) {
	    /* Not a numeric value - Must be the user name */
	    poUsr = getpwnam (pcStr);
	    if (!poUsr) {
		fprintf (stderr, "Error: %s - wrong userId\n", pcId);
		goto Error;
	    }
	    uid = poUsr->pw_uid;
	}

	/* Free working copy of User/Group Id's string */
	free (pcIds);

    }				/* if (pcU) */

    /* Switch to root if run as ordinary user */
    if (getuid () != 0) {
	status = setuid (0);
	if (status == -1) {
	    fprintf (stderr, "%s: must be run as root.\n", pcThis);
	    goto Error;
	}
    }

    /* Switch to new group first, then to user id's */
    status = setgid (gid);
    if (status == -1)
	perror ("setgid");
    status = setuid (uid);
    if (status == -1) {
	perror ("setuid");
	goto Error;
    }

    argv2 = argv + optind;
    execvp (argv2[0], argv2);
    perror (argv2[0]);

  Error:
    if (pcIds)
	free (pcIds);
    return (~0);
}

/*
$Log: runas.c,v $
Revision 1.8  2007/05/10 06:23:40  RogerSeguin

Revision 1.1  2003/11/20 11:40:18  RogerSeguin
Initial revision

*/
