/*************************************************** */
/* Rule Set Based Access Control                     */
/*                                                   */
/* Author and (c) 1999-2021: Amon Ott <ao@rsbac.org> */
/*                                                   */
/* Last modified: 24/Mar/2021                        */
/*************************************************** */

#include <asm/unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>
#include <rsbac/error.h>
#include <rsbac/cap_getname.h>
#include "nls.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <signal.h>
#include <sched.h>

#ifndef CLONE_NEWNS
#define CLONE_NEWNS 0x00020000
#endif

#define CAP_TO_INDEX(x)     ((x) >> 5)        /* 1 << 5 == bits in __u32 */

char * progname;

void use(void)
    {
      printf(gettext("%s (RSBAC %s)\n***\n"), progname, VERSION);
      printf(gettext("Use: %s [flags] [-I addr] [-R dir] [-C cap-list] prog args\n"), progname);
      printf(gettext("This program will put the process into a jail with chroot to path,\n"));
      printf(gettext("ip address IP and then execute prog with args\n"));
      printf(gettext(" -h = this help, -- = no more flags,\n"));
      printf(gettext("-I addr = limit to IP address,\n"));
      printf(gettext("-R dir  = chroot to dir,\n"));
      printf(gettext("-V set  = use virtual user set,\n"));
      printf(gettext("-N = enclose process in its private namespace,\n"));
      printf(gettext("-C cap-list  = limit Linux capabilities for jailed processes,\n"));
      printf(gettext("               use bit-vector, numeric value or list names of desired caps,\n"));
      printf(gettext("               A = all, FS_MASK = all filesystem related,\n"));
      printf(gettext("-L = list all Linux capabilities,\n"));
      printf(gettext("-S = list all SCD targets,\n"));
      printf(gettext("-v = verbose, -i = allow access to IPC outside this jail,\n"));
      printf(gettext("-P = allow access to IPC in the parent jail,\n"));
      printf(gettext("-y = allow access to IPC in the syslog jail,\n"));
      printf(gettext("-Y = this is the syslog jail,\n"));
      printf(gettext("-T = allow access from current jail to processes in subjail,\n"));
      printf(gettext("-n = allow all network families, not only UNIX and INET (IPv4),\n"));
      printf(gettext("-K = allow NETLINK network family,\n"));
      printf(gettext("-r = allow INET (IPv4) raw sockets (e.g. for ping),\n"));
      printf(gettext("-a = auto-adjust INET any address 0.0.0.0 to jail address, if set,\n"));
      printf(gettext("-o = additionally allow to/from remote INET (IPv4) address 127.0.0.1,\n"));
      printf(gettext("-d = allow read access on devices, -D allow write access\n"));
      printf(gettext("-e = allow GET_STATUS_DATA on devices, -E allow MODIFY_SYSTEM_DATA\n"));
      printf(gettext("-F = allow MODIFY_SYSTEM_DATA on network devices\n"));
      printf(gettext("-t = allow *_OPEN on tty devices\n"));
      printf(gettext("-s = allow to create with / set mode to suid\n"));
      printf(gettext("-u = allow to mount/umount\n"));
      printf(gettext("-G scd ... = allow GET_STATUS_DATA on these scd targets\n"));
      printf(gettext("-M scd ... = allow MODIFY_SYSTEM_DATA on these scd targets\n"));
      printf(gettext("Deprecated old options, please use -G and -M:\n"));
      printf(gettext("-l = allow to modify rlimits (-M rlimit),\n"));
      printf(gettext("-c = allow to modify system clock (-M clock time_strucs),\n"));
      printf(gettext("-m = allow to lock memory (-M mlock),\n"));
      printf(gettext("-p = allow to modify priority (-M priority),\n"));
      printf(gettext("-k = allow to get kernel symbols (-G ksyms)\n"));
    }

struct clone_args {
	rsbac_version_t version;
	char * rootdir;
	rsbac_jail_ip_t ip;
	rsbac_jail_flags_t flags;
	rsbac_cap_vector_t max_caps;
	rsbac_jail_scd_vector_t scd_get;
	rsbac_jail_scd_vector_t scd_modify;
	char ** argv;
};

int rsbac_jail_ns(void * args)
{
	int res;
	struct clone_args * a = (struct clone_args *) args;
	
	res = rsbac_jail(a->version, a->rootdir, a->ip,
			a->flags, a->max_caps,
			a->scd_get, a->scd_modify);
	if (res)
		return res;
	if (a->rootdir) {
		res = chdir("/");
		if (res)
			return res;
	}
	res = execvp(a->argv[1],&a->argv[1]);

	return res;
}

int main(int argc, char ** argv)
{
  int res = 0;
  rsbac_jail_flags_t jail_flags = 0;
  int verbose = 0;
  struct in_addr addr;
  rsbac_jail_ip_t ip = 0;
  char * rootdir = NULL;
//  rsbac_um_set_id_t um_set = RSBAC_UM_SET_AUTO;
  rsbac_cap_vector_t max_caps;
  max_caps.cap[0] = -1;
  max_caps.cap[1] = -1;
  rsbac_jail_scd_vector_t scd_get = 0;
  rsbac_jail_scd_vector_t scd_modify = 0;
  rsbac_um_set_t vset = RSBAC_UM_VIRTUAL_KEEP;
  int err;
  unsigned int namespace = 0;
  u_int stopflags = FALSE;

  locale_init();

  progname = argv[0];
  inet_aton("0.0.0.0", &addr);
  ip = addr.s_addr;

  while((argc > 1) && (argv[1][0] == '-') && !stopflags)
    {
      char * pos = argv[1];
      pos++;
      while(*pos)
        {
          switch(*pos)
            {
              case '-':
                stopflags = TRUE;
                break;
              case 'h':
                use();
                return 0;
              case 'v':
                verbose++;
                break;
	      case 'N':
		namespace = 1;
		break;
              case 'i':
                jail_flags |= JAIL_allow_external_ipc;
                break;
              case 'P':
                jail_flags |= JAIL_allow_parent_ipc;
                break;
              case 'y':
                jail_flags |= JAIL_allow_ipc_to_syslog;
                break;
              case 'Y':
                jail_flags |= JAIL_this_is_syslog;
                break;
              case 'T':
                jail_flags |= JAIL_allow_process_by_parent;
                break;
              case 's':
                jail_flags |= JAIL_allow_suid_files;
                break;
              case 'n':
                jail_flags |= JAIL_allow_all_net_family;
                break;
              case 'K':
                jail_flags |= JAIL_allow_netlink;
                break;
              case 'r':
                jail_flags |= JAIL_allow_inet_raw;
                break;
              case 'a':
                jail_flags |= JAIL_auto_adjust_inet_any;
                break;
              case 'o':
                jail_flags |= JAIL_allow_inet_localhost;
                break;
              case 'd':
                jail_flags |= JAIL_allow_dev_read;
                break;
              case 'D':
                jail_flags |= JAIL_allow_dev_write;
                break;
              case 'e':
                jail_flags |= JAIL_allow_dev_get_status;
                break;
              case 'E':
                jail_flags |= JAIL_allow_dev_mod_system;
                break;
              case 'F':
                jail_flags |= JAIL_allow_netdev_mod_system;
                break;
              case 't':
                jail_flags |= JAIL_allow_tty_open;
                break;
              case 'u':
                jail_flags |= JAIL_allow_mount;
                break;
              case 'l':
                scd_modify |= RSBAC_SCD_VECTOR(ST_rlimit);
                break;
              case 'c':
                scd_modify |= RSBAC_SCD_VECTOR(ST_clock);
                scd_modify |= RSBAC_SCD_VECTOR(ST_time_strucs);
                break;
              case 'm':
                scd_modify |= RSBAC_SCD_VECTOR(ST_mlock);
                break;
              case 'p':
                scd_modify |= RSBAC_SCD_VECTOR(ST_priority);
                break;
              case 'k':
                scd_modify |= RSBAC_SCD_VECTOR(ST_ksyms);
                break;
              case 'G':
                if(argc > 2)
                  {
                    int scd;

                    while(argc > 2)
                      {
                        scd = get_scd_type_nr(argv[2]);
                        if(scd == ST_none)
                          {
                            scd = strtol(argv[2],0,10);
                            if(   (scd >= ST_none)
                               || (   (scd == 0)
                                   && strcmp(argv[2],"0")
                                  )
                              )
                              {
                                if(!strcmp(argv[2],"A"))
                                  {
                                    scd_get = -1;
                                  }
                                else
                                if(!strcmp(argv[2],"UA"))
                                  {
                                    scd_get = 0;
                                  }
                                else
                                  { /* end of scd */
                                    break;
                                  }
                              }
                          }
                        else
                          {
                            scd_get |= RSBAC_SCD_VECTOR(scd);
                          }
                        argv++;
                        argc--;
                      }
                  }
                else
                  fprintf(stderr, gettext("%s: missing SCDs for parameter %c\n"), progname, *pos);
                break;
              case 'M':
                if(argc > 2)
                  {
                    int scd;

                    while(argc > 2)
                      {
                        scd = get_scd_type_nr(argv[2]);
                        if(scd == ST_none)
                          {
                            scd = strtol(argv[2],0,10);
                            if(   (scd >= ST_none)
                               || (   (scd == 0)
                                   && strcmp(argv[2],"0")
                                  )
                              )
                              {
                                if(!strcmp(argv[2],"A"))
                                  {
                                    scd_modify = -1;
                                  }
                                else
                                if(!strcmp(argv[2],"UA"))
                                  {
                                    scd_modify = 0;
                                  }
                                else
                                  { /* end of scd */
                                    break;
                                  }
                              }
                          }
                        else
                          {
                            scd_modify |= RSBAC_SCD_VECTOR(scd);
                          }
                        argv++;
                        argc--;
                      }
                  }
                else
                  fprintf(stderr, gettext("%s: missing SCDs for parameter %c\n"), progname, *pos);
                break;
              case 'I':
                if(argc > 2)
                  {
                    err = inet_aton(argv[2], &addr);
                    error_exit(err);
                    ip = addr.s_addr;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing address for parameter %c\n"), progname, *pos);
                break;
              case 'R':
                if(argc > 2)
                  {
                    rootdir = argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing dirname for parameter %c\n"), progname, *pos);
                break;
              case 'V':
                if(argc > 2)
                  {
                    vset = strtol(argv[2],0,0);
                    argc--;
                    argv++;
                  }
                else {
                  fprintf(stderr, gettext("%s: missing virtual set number for parameter %c\n"), progname, *pos);
                  exit(1);
                }
                break;
              case 'C':
                if(argc > 2)
                  {
                    int cap;

                    max_caps.cap[0] = (__u32) 0;
		    max_caps.cap[1] = (__u32) 0;
                    while(argc > 2)
                      {
                        int bitlen;

                        bitlen = strlen(argv[2]);
                        if(bitlen == CAP_NONE || bitlen == CAP_NONE_OLD || bitlen == CAP_NONE_OLD_OLD)
                          {
                            int j;
                            rsbac_cap_vector_t tmp_cv;

                            for(j=0; j<bitlen; j++)
                            if(   (argv[2][j] != '0')
                               && (argv[2][j] != '1')
                              )
                              {
                                break;
                              }
                            strcaptokcap(argv[2], &tmp_cv);
                            max_caps.cap[0] |= tmp_cv.cap[0];
			    max_caps.cap[1] |= tmp_cv.cap[1];
                            argv++;
                            argc--;
                            continue;
                          }
                        cap = get_cap_nr(argv[2]);
                        if(cap == CAP_NONE)
                          {
                            cap = strtol(argv[2],0,10);
                            if(   (cap >= CAP_NONE)
                               || (   (cap == 0)
                                   && strcmp(argv[2],"0")
                                  )
                              )
                              {
                                if(!strcmp(argv[2],"A"))
                                  {
                                    max_caps.cap[0] = (__u32) -1;
				    max_caps.cap[1] = (__u32) -1;
                                  }
                                else
                                if(!strcmp(argv[2],"UA"))
                                  {
                                    max_caps.cap[0] = (__u32) 0;
				    max_caps.cap[1] = (__u32) 0;
                                  }
                                else
                                if(!strcmp(argv[2],"FS_MASK"))
                                  { /* famous problem here */
                                    max_caps.cap[0] |= CAP_FS_MASK;
                                  }
                                else
                                  { /* end of caps */
                                    break;
                                  }
                              }
                          }
                        else
                          {
                            max_caps.cap[CAP_TO_INDEX(cap)] |= ((__u32) 1 << cap);
                          }
                        argv++;
                        argc--;
                      }
                  }
                else
                  fprintf(stderr, gettext("%s: missing caps for parameter %c\n"), progname, *pos);
                break;
              case 'L':
                {
                  char tmp[RSBAC_MAXNAMELEN];
                  int i;

                  for(i=0; i<RSBAC_CAP_MAX; i++)
                    printf("%s\n", get_cap_name(tmp, i));
                  exit(0);
                }
              case 'S':
                {
                  char tmp[RSBAC_MAXNAMELEN];
                  int i;

                  for(i=0; i<ST_none; i++)
                    printf("%s\n", get_scd_type_name(tmp, i));
                  exit(0);
                }

              default:
                fprintf(stderr, gettext("%s: unknown parameter %c\n"), progname, *pos);
                exit(1);
            }
          pos++;
        }
      argv++;
      argc--;
    }

  if (argc > 1)
    {
      if(verbose) {
        if(rootdir)
          printf(gettext("%s: executing %s in jail at %s with IP %s, flags %u, caps %u, scd_get %u, scd_modify %u, namespace %u, vset %u\n"),
                 progname,
                 argv[1],
                 rootdir,
                 inet_ntoa(addr),
                 jail_flags,
                 max_caps.cap[0],
                 scd_get,
                 scd_modify,
		 namespace,
		 vset);
        else
          printf(gettext("%s: executing %s in jail (no chroot) with IP %s, flags %u, caps %u, scd_get %u, scd_modify %u, namespace %u, vset %u\n"),
                 progname,
                 argv[1],
                 inet_ntoa(addr),
                 jail_flags,
                 max_caps.cap[0],
                 scd_get,
                 scd_modify,
		 namespace,
		 vset);
      }
      if (namespace) {
	      pid_t pid;
	      int status;
	      struct clone_args args;
	      args.version = RSBAC_JAIL_VERSION;
	      args.rootdir = rootdir;
	      args.ip = ip;
	      args.flags = jail_flags;
	      args.max_caps = max_caps;
	      args.scd_get = scd_get;
	      args.scd_modify = scd_modify;
	      args.argv = argv;

	      signal(SIGCHLD, SIG_DFL);
	      pid = syscall(__NR_clone, CLONE_NEWNS | SIGCHLD, 0);
	      switch (pid) {
		      case -1:
			      perror("clone");
			      exit(1);
		      case 0:
			      if(vset != RSBAC_UM_VIRTUAL_KEEP) {
				res = rsbac_um_select_vset(vset);
				error_exit(res);
			      }
			      rsbac_jail_ns(&args);
		      default:
			      wait4(pid, &status, 0, 0);
			      exit(EXIT_SUCCESS);
	      }
      }
      if(vset != RSBAC_UM_VIRTUAL_KEEP) {
	res = rsbac_um_select_vset(vset);
	error_exit(res);
      }
      res = rsbac_jail(RSBAC_JAIL_VERSION, rootdir, ip,
                       jail_flags, max_caps, scd_get, scd_modify);
      error_exit(res);
      if(rootdir)
        {
          /* Already done in syscall rsbac_jail, but better repeat */
          res = chdir("/");
          error_exit(res);
        }
      res = execvp(argv[1],&argv[1]);
      error_exit(res);
    }
  else
    {
      use();
      return 1;
    }
  return (res);
}
