/*
  file: setuidruby.c

    - setuidruby is a wrapper on ruby which will run ruby as another user

    - to compile

        make

      or, __only__ if you __know__ what you are doing

        make mode=6777

    - the user under which to execute ruby and an optional path to a ruby
      interpreter must be specified as the first argument to setuidruby.  for
      example:

        [ahoward@localhost setuidruby-0.0.0]$ cat ./a.rb
        require "yaml"

        y 'euid' => Process::euid,
          'uid' => Process::uid,
          'egid' => Process::egid,
          'gid' => Process::gid

        [ahoward@localhost setuidruby-0.0.0]$ setuidruby ahoward ./a.rb
        ---
        egid: 500
        gid: 500
        euid: 500
        uid: 500

        [ahoward@localhost setuidruby-0.0.0]$ setuidruby jsherry:/usr/bin/ruby182 ./a.rb
        ---
        egid: 510
        gid: 510
        euid: 509
        uid: 509

      if the path to ruby, which must be absolute, is not specifed the compile
      time default of /usr/bin/ruby is used.

      obviously setuidruby can be used in a shebang line:

        [ahoward@localhost setuidruby-0.0.0]$ cat b.rb
        #! /usr/bin/setuidruby ahoward:/usr/bin/ruby190
        require "yaml"

        y 'euid' => Process::euid,
          'uid' => Process::uid,
          'egid' => Process::egid,
          'gid' => Process::gid

        [ahoward@localhost setuidruby-0.0.0]$ ./b.rb
        ---
        egid: 500
        gid: 500
        euid: 500
        uid: 500


    - this program could be __very__ dangerous to your system's health: use
      wisely.  you are responsible.
*/


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


static int
xdie (const char *const msg, int err, const char *const file, int lineno)
{
  if (msg)
    {
      fprintf (stderr, "msg(%s) strerror(%s) errno(%d) @ %s:%d\n", msg,
	       strerror (errno), errno, file, lineno);
    }
  else
    {
      fprintf (stderr, "strerror(%s) errno(%d) @ %s:%d\n", strerror (errno),
	       errno, file, lineno);
    }
  exit (err);
}

#define usr_bin_ruby "/usr/bin/ruby"
#define die(msg) do{xdie(msg,errno,__FILE__,__LINE__);}while(0)
#define usage "setuidruby user[:/path/to/ruby (default /usr/bin/ruby)] [rubyargs]"


int
main (argc, argv, env)
     int argc;
     char **argv;
     char **env;
{
  char *user;
  char *ruby;
  char *cp;
  struct passwd *entry;
  uid_t pw_uid;
  gid_t pw_gid;

  user = argv[1];
  argv++;
  if (user == NULL)
    {
      die (usage);
    }

  for (cp = user; *cp && *cp != ':'; cp++);
  if (!*cp)
    {
      ruby = usr_bin_ruby;
    }
  else
    {
      *cp = '\0';
      cp++;
      ruby = *cp ? cp : usr_bin_ruby;
    }

  entry = getpwnam (user);

  if (entry == NULL)
    {
      die ("must set SETUSER or SETUID");
    }

  pw_uid = entry->pw_uid;
  pw_gid = entry->pw_gid;

  setregid (pw_gid, pw_gid);
  setfsgid (pw_gid);

  setreuid (pw_uid, pw_uid);
  setfsuid (pw_uid);

  execv (ruby, argv);
  die (ruby);
}
