/*
 * set uid and gid, then run something, with a time limit
 *
 * invoked as:
 *
 *   ./run [--setuidgid uid gid] [--chdir dir] [-<secs>] prog [args...]
 *
 * for example:
 *
 *   ./run --setuidgid 3012 3012 --chdir /home/frodo/a3 \
 *      -10 gdb -x /local-bin/gdb.batch -batch ./p1
 *
 * where -<secs> optionally specifies the time limit (default 1800 seconds)
 *
 * R. Perry, Nov. 2004
 *
 * Updated June 2012 for Linux
 *
 * Updated October 2012 to use execvpe() with restricted environment,
 * and to set HOME from the --chdir argument
 */

// for www.ece.villanova.edu:
//
#define execvpe execve

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>

int putenv(char *string);

static char *progname; /* my name */

static pid_t pid; /* child pid */

static int limit = 1800; /* time limit */

void report_signal( const char *what, int sig)
{
  const char *desc = "";

  switch( sig)
  {
    case SIGHUP: desc = "SIGHUP - Hangup";
	break;
    case SIGINT: desc = "SIGINT - Interrupt";
	break;
    case SIGQUIT: desc = "SIGQUIT - Quit";
	break;
    case SIGILL: desc = "SIGILL - Illegal instruction";
	break;
    case SIGTRAP: desc = "SIGTRAP - Trace trap";
	break;
    case SIGABRT: desc = "SIGABRT - Abort";
	break;
    case SIGBUS: desc = "SIGBUS - BUS error";
	break;
    case SIGFPE: desc = "SIGFPE - Floating-point exception";
	break;
    case SIGKILL: desc = "SIGKILL - Kill";
	break;
    case SIGUSR1: desc = "SIGUSR1 - User-defined signal 1";
	break;
    case SIGSEGV: desc = "SIGSEGV - Segmentation violation";
	break;
    case SIGUSR2: desc = "SIGUSR2 - User-defined signal 2";
	break;
    case SIGPIPE: desc = "SIGPIPE - Broken pipe";
	break;
    case SIGALRM: desc = "SIGALRM - Alarm clock";
	break;
    case SIGTERM: desc = "SIGTERM - Termination";
	break;
    case SIGSTKFLT: desc = "SIGSTKFLT - Stack fault";
	break;
    case SIGCHLD: desc = "SIGCHLD - Child status has changed";
	break;
    case SIGCONT: desc = "SIGCONT - Continue";
	break;
    case SIGSTOP: desc = "SIGSTOP - Stop";
	break;
    case SIGTSTP: desc = "SIGTSTP - Keyboard stop";
	break;
    case SIGTTIN: desc = "SIGTTIN - Background read from tty";
	break;
    case SIGTTOU: desc = "SIGTTOU - Background write to tty";
	break;
    case SIGURG: desc = "SIGURG - Urgent condition on socket";
	break;
    case SIGXCPU: desc = "SIGXCPU - CPU limit exceeded";
	break;
    case SIGXFSZ: desc = "SIGXFSZ - File size limit exceeded";
	break;
    case SIGVTALRM: desc = "SIGVTALRM - Virtual alarm clock";
	break;
    case SIGPROF: desc = "SIGPROF - Profiling alarm clock";
	break;
    case SIGWINCH: desc = "SIGWINCH - Window size change";
	break;
    case SIGPOLL: desc = "SIGPOLL - Pollable event occurred";
	break;
    case SIGPWR: desc = "SIGPWR - Power failure restart";
	break;
    case SIGSYS: desc = "SIGSYS - Bad system call";
	break;
  }

  fprintf( stderr, "\n%s by signal %d - %s\n", what, sig, desc);
}

void cleanup( int sig)
{
  if( sig == SIGALRM)
  {
    fprintf( stderr, "\n---\n%s: time limit of %d seconds has been exceeded\n", progname, limit);
    fflush( stderr);
  }

  if( geteuid() != 0)
    kill( -1, SIGKILL); /* kill all, we better not be running as root! */
  else
    kill( pid, SIGKILL);

  exit(1);
}

// enviroment for execvpe()
//
char home[BUFSIZ] = "/";

char *envp[] =
{
  "PATH=/vecr/bin:/opt/jdk/bin:/opt/bin:/bin:/usr/bin:/sbin:/usr/sbin:.",
  "LD_PRELOAD=/vecr/IO/setvbuf.so",
  home,
  0
};
  
int main( int argc, char *argv[])
{
  uid_t uid;
  gid_t gid;
  gid_t grouplist[1];

/* get my name */

  progname = strrchr( argv[0], '/');
  progname = progname ? progname + 1 : argv[0];

/*** Must be the first command-line option: --setuidgid uid gid ***/

    if( argc >= 4 && strcmp( argv[1], "--setuidgid") == 0)
    {
	uid = atoi( argv[2]);
	gid = atoi( argv[3]);

	grouplist[0] = gid;

	if( setgroups( 1, grouplist))
	{
	    fprintf( stderr, "%s: setgroups() failed: %s\n", progname, strerror(errno));
	    exit(1);
	}

	if( setgid( gid))
	{
	    fprintf( stderr, "%s: setgid() failed: %s\n", progname, strerror(errno));
	    exit(1);
	}

	if( setuid( uid))
	{
	    fprintf( stderr, "%s: setuid() failed: %s\n", progname, strerror(errno));
	    exit(1);
	}

	argc -= 3;
	argv += 3;
    }

/*** Must be the first remaining command-line option: --chdir dir ***/

    if( argc >= 3 && strcmp( argv[1], "--chdir") == 0)
    {
	if( chdir( argv[2]))
	{
	    fprintf( stderr, "%s: chdir() failed: %s\n", progname, strerror(errno));
	    exit(1);
	}

	strcpy( home, "HOME=");
	strncat( home, argv[2], BUFSIZ-10);

	argc -= 2;
	argv += 2;
    }

/* check for -<secs> option */

  if( argc > 1 && argv[1][0] == '-')
  {
    limit = atoi( argv[1]+1);
    --argc, ++argv;
  }

/* check usage */

  if( argc < 2)
  {
    fprintf( stderr, "Usage: %s [--setuidgid uid gid] [--chdir dir] "
      "[-<secs>] prog [args...]\n", progname);
    return 1;
  }

/* reset errno */

  errno = 0;

/* try to fork */

  if( (pid = fork()) < 0)
  {
    fprintf( stderr, "%s: fork: %s\n", progname, strerror(errno));
    return 1;
  }
  
  if( pid == 0) /* child */
  {
    int e = 0;
    struct rlimit rl;

  // set limits
  // 
  //  ulimit				setrlimit
  //  -----------------------------	---------
  //  time(seconds)         60		RLIMIT_CPU (seconds)
  //  file(blocks)          1024	RLIMIT_FSIZE (bytes)
  //  data(kbytes)          8192	RLIMIT_DATA (bytes)
  //  stack(kbytes)         8192	RLIMIT_STACK (bytes)
  //  coredump(blocks)      0		RLIMIT_CORE (bytes)
  //  memory(kbytes)        65536	RLIMIT_RSS (bytes)
  //  locked memory(kbytes) 64		RLIMIT_MEMLOCK (bytes)
  //  process               64		RLIMIT_NPROC
  //  nofiles               64		RLIMIT_NOFILE
  //  vmemory(kbytes)       unlimited	RLIMIT_AS (bytes?)
  //  locks                 64		RLIMIT_LOCKS
  //
    rl.rlim_cur = rl.rlim_max = 60;
    if( setrlimit( RLIMIT_CPU, &rl)) { ++e; perror( "setrlimit cpu"); }

    rl.rlim_cur = rl.rlim_max = 1024*512;
    if( setrlimit( RLIMIT_FSIZE, &rl)) { ++e; perror( "setrlimit fsize"); }

    rl.rlim_cur = rl.rlim_max = 8192*1024;
    if( setrlimit( RLIMIT_DATA, &rl)) { ++e; perror( "setrlimit data"); }

    rl.rlim_cur = rl.rlim_max = 8192*1024;
    if( setrlimit( RLIMIT_STACK, &rl)) { ++e; perror( "setrlimit stack"); }

    rl.rlim_cur = rl.rlim_max = 0;
    if( setrlimit( RLIMIT_CORE, &rl)) { ++e; perror( "setrlimit core"); }

    rl.rlim_cur = rl.rlim_max = 65536*1024;
    if( setrlimit( RLIMIT_RSS, &rl)) { ++e; perror( "setrlimit memory"); }

#if 0 // doesn't work on www.ece.villanova.edu
    rl.rlim_cur = rl.rlim_max = 64*1024;
    if( setrlimit( RLIMIT_MEMLOCK, &rl)) { ++e; perror( "setrlimit memlock"); }
#endif

    rl.rlim_cur = rl.rlim_max = 64;
    if( setrlimit( RLIMIT_NPROC, &rl)) { ++e; perror( "setrlimit process"); }

    rl.rlim_cur = rl.rlim_max = 64;
    if( setrlimit( RLIMIT_NOFILE, &rl)) { ++e; perror( "setrlimit nfiles"); }

    rl.rlim_cur = rl.rlim_max = 64;
    if( setrlimit( RLIMIT_LOCKS, &rl)) { ++e; perror( "setrlimit locks"); }

    if( putenv( "PATH=/vecr/bin:/opt/jdk/bin:/opt/bin:/bin:/usr/bin:/sbin:/usr/sbin:."))
    { ++e; perror( "putenv PATH"); }

    if( e) { fflush( stderr); _exit(1); }

    execvpe( argv[1], argv+1, envp); /* should not return */

    fprintf( stderr, "%s: execvp: %s: %s\n", progname, argv[1], strerror(errno));
    fflush( stderr);
    _exit(0);
  }

/* parent */

  int status;
  pid_t wait_result;

  signal( SIGALRM, cleanup); /* set alarm */
  alarm( limit);

/* wait for child to exit */

  wait_result = waitpid( pid, &status, WUNTRACED);

  alarm(0); /* cancel alarm */

  if( wait_result == (pid_t) -1)
  {
    fprintf( stderr, "%s: pid = %d, wait_result = %d, error = %s\n",
      progname, (int) pid, (int) wait_result, strerror(errno));
    fflush( stderr);
    return 1;
  }

/* wait_result == pid (of some child) */

  if( WIFSTOPPED(status)) /* child has stopped */
  {
    report_signal( "stopped", WSTOPSIG(status));
    fflush( stderr);
    cleanup( WSTOPSIG(status));
    return 1;
  }

  if( WIFSIGNALED(status)) /* child was terminated by a signal */
  {
    report_signal( "terminated", WTERMSIG(status));
    fflush( stderr);
    return 1;
  }

  if( WIFEXITED(status)) /* child has exited */
  {
    return WEXITSTATUS(status); /* return child exit status */
  }

  return 0; /* shouldn't get here */
}