#! /usr/bin/python -E
# Authors: Dan Walsh <dwalsh@redhat.com>
# Authors: Josh Cogliati
#
# Copyright (C) 2009  Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#

import os, sys, getopt, socket, random, fcntl, shutil, re, subprocess
import selinux
import signal
from tempfile import mkdtemp
import pwd

PROGNAME = "policycoreutils"

import gettext
gettext.bindtextdomain(PROGNAME, "/usr/share/locale")
gettext.textdomain(PROGNAME)

try:
       gettext.install(PROGNAME,
                       localedir = "/usr/share/locale",
                       unicode=False,
                       codeset = 'utf-8')
except IOError:
       import __builtin__
       __builtin__.__dict__['_'] = unicode


DEFAULT_TYPE = "sandbox_t"
DEFAULT_X_TYPE = "sandbox_x_t"
X_FILES = {}

random.seed(None)

def sighandler(signum, frame):
    signal.signal(signum,  signal.SIG_IGN)
    os.kill(0, signum)
    raise KeyboardInterrupt

def setup_sighandlers():
    signal.signal(signal.SIGHUP,  sighandler)
    signal.signal(signal.SIGQUIT, sighandler)
    signal.signal(signal.SIGTERM, sighandler)

def error_exit(msg):
    sys.stderr.write("%s: " % sys.argv[0])
    sys.stderr.write("%s\n" % msg)
    sys.stderr.flush()
    sys.exit(1)

def reserve(level):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.bind("\0%s" % level)
    fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)

def gen_mcs():
       while True:
              i1 = random.randrange(0, 1024)
              i2 = random.randrange(0, 1024)
              if i1 == i2:
                     continue
              if i1 > i2:
                     tmp = i1
                     i1 = i2
                     i2 = tmp
                     level = "s0:c%d,c%d" % (i1, i2)
              level = "s0:c%d,c%d" % (i1, i2)
              try:
                     reserve(level)
              except socket.error:
                     continue
              break
       return level

def gen_context(setype, level=None):
    if not level:
           level = gen_mcs()

    con = selinux.getcon()[1].split(":")

    execcon = "%s:%s:%s:%s" % (con[0], con[1], setype, level)
    
    filecon = "%s:%s:%s:%s" % (con[0], 
                               "object_r", 
                               "%s_file_t" % setype[:-2], 
                               level)
    return execcon, filecon

def copyfile(file, dir, dest):
       import re
       if file.startswith(dir):
              dname = os.path.dirname(file)
              bname = os.path.basename(file)
              if dname == dir:
                     dest = dest + "/" + bname
              else:
                     newdir = re.sub(dir, dest, dname)
                     os.makedirs(newdir)
                     dest = newdir + "/" + bname

              if os.path.isdir(file):
                     shutil.copytree(file, dest)
              else:
                     shutil.copy2(file, dest)
              X_FILES[file] = (dest, os.path.getmtime(dest))

def copyfiles(newhomedir, newtmpdir, files):
       homedir=pwd.getpwuid(os.getuid()).pw_dir
       for f in files:
              copyfile(f,homedir, newhomedir)
              copyfile(f,"/tmp", newtmpdir)

def savefile(new, orig, X_ind):
       copy = False
       if(X_ind):
              import gtk
              dlg = gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO,
                                      gtk.BUTTONS_YES_NO,
                                      _("Do you want to save changes to '%s' (Y/N): ") % orig)
              dlg.set_title(_("Sandbox Message"))
              dlg.set_position(gtk.WIN_POS_MOUSE)
              dlg.show_all()
              rc = dlg.run()
              dlg.destroy()
              if rc == gtk.RESPONSE_YES:
                     copy = True
       else:
              ans = raw_input(_("Do you want to save changes to '%s' (y/N): ") % orig)
              if(re.match(_("[yY]"),ans)):
                     copy = True
       if(copy):
              shutil.copy2(new,orig)

def setup_executable(execfile, command):
       fd = open(execfile, "w+")
       fd.write("""
#! /bin/sh
#TITLE: %s
/usr/bin/test -r ~/.xmodmap && /usr/bin/xmodmap ~/.xmodmap
/usr/bin/matchbox-window-manager -use_titlebar no &
WM_PID=$!
%s
kill -TERM $WM_PID  2> /dev/null
""" % (command, command))
       fd.close()
       os.chmod(execfile, 0700)

def setup_session(execfile, command="/etc/gdm/Xsession"):
       fd = open(execfile, "w+")
       fd.write("""
#!/bin/sh
#TITLE: %s
%s
""" % (command, command))
       fd.close()
       os.chmod(execfile, 0700)

if __name__ == '__main__':
    setup_sighandlers()
    if selinux.is_selinux_enabled() != 1:
        error_exit("Requires an SELinux enabled system")
        
    init_files = []

    def usage(message = ""):
        text = _("""
sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [[-i file ] ...] [ -t type ] command
sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [[-i file ] ...] [ -t type ] -S
""")
        error_exit("%s\n%s" % (message, text))

    setype = DEFAULT_TYPE
    X_ind = False
    home_and_temp = False
    level=None
    newhomedir = None
    newtmpdir = None
    existing_home = False
    existing_temp = False
    session = False
    try:
           gopts, cmds = getopt.getopt(sys.argv[1:], "l:i:hSt:XI:MH:T:", 
                                       ["help",
                                        "include=", 
                                        "includefile=", 
                                        "type=",
                                        "mount",
                                        "homedir=",
                                        "tmpdir=",
                                        "session",
                                        "level="
                                        ])
           for o, a in gopts:
                  if o == "-t" or o == "--type":
                         setype = a

                  if o == "-l" or o == "--level":
                         level = a
                         
                  if o == "-i" or o == "--include":
                         rp = os.path.realpath(a)
                         if rp not in init_files:
                                init_files.append(rp)
                         
                  if o == "-I" or o == "--includefile":
                         fd = open(a, "r")
                         for i in fd.read().split("\n"):
                                if os.path.exists(i):
                                       rp = os.path.realpath(i)
                                       if rp not in init_files:
                                              init_files.append(rp)
                                       
                         fd.close
                         
                  if o == "-X":
                         if DEFAULT_TYPE == setype:
                                setype = DEFAULT_X_TYPE
                         X_ind = True
                         home_and_temp = True
                  if o == "-M" or o == "--mount":
                         home_and_temp = True

                  if o == "-H" or o == "--homedir":
                         existing_home = True
                         newhomedir = a
                  if o == "-T" or o == "--tmpdir":
                         existing_temp = True
                         newtmpdir = a
                  if o == "-h" or o == "--help":
                         usage(_("Usage"));

                  if o == "-S" or o == "--session":
                         session = True
                         homedir=pwd.getpwuid(os.getuid()).pw_dir
                         if setype in (DEFAULT_TYPE, DEFAULT_X_TYPE):
                                setype = selinux.getcon()[1].split(":")[2]
            
           if len(cmds) == 0 and not session:
                  usage(_("Command required"))

           if (existing_home or existing_temp) and not home_and_temp:
                  usage(_("-M required when specifying home directory or temp directory"))
           execcon, filecon = gen_context(setype, level)
           rc = -1

           if not session and cmds[0][0] != "/" and cmds[0][:2] != "./" and cmds[0][:3] != "../":
                  for i in  os.environ["PATH"].split(':'):
                         f = "%s/%s" % (i, cmds[0])
                         if os.access(f, os.X_OK):
                                cmds[0] = f
                                break

           try:
                  if home_and_temp:
                         if not os.path.exists("/usr/sbin/seunshare"):
                                raise ValueError("""/usr/sbin/seunshare required for sandbox -M, to install you need to execute 
#yum install /usr/sbin/seunshare""")
                         import warnings
                         warnings.simplefilter("ignore")
                         if existing_home:
                                if not os.path.isdir(newhomedir):
                                       raise IOError("Home directory "+newhomedir+" not found")
                                if not level and not session:
                                       chcon =  ("/usr/bin/chcon -R %s %s" % (filecon, newhomedir)).split()
                                       rc = os.spawnvp(os.P_WAIT, chcon[0], chcon)
                         else:
                                newhomedir = mkdtemp(dir=".", prefix=".sandbox")
                                if session:
                                       chcon =  ("/usr/bin/chcon --reference %s %s" %( homedir,  (newhomedir))).split()
                                else:
                                       chcon =  ("/usr/bin/chcon %s %s" % (filecon, newhomedir)).split()
                                rc = os.spawnvp(os.P_WAIT, chcon[0], chcon)

                         if existing_temp:
                                if not os.path.isdir(newtmpdir):
                                       raise IOError("Temp directory "+newtmpdir+" not found")                
                                if not level and not session:
                                       chcon =  ("/usr/bin/chcon -R %s %s" % (filecon, newtmpdir)).split()
                                       rc = os.spawnvp(os.P_WAIT, chcon[0], chcon)
                         else:
                                newtmpdir = mkdtemp(dir="/tmp", prefix=".sandbox")
                                if session:
                                       chcon =  ("/usr/bin/chcon --reference /tmp %s" % (newtmpdir)).split()
                                else:
                                       chcon =  ("/usr/bin/chcon %s %s" % (filecon, newtmpdir)).split()
                                rc = os.spawnvp(os.P_WAIT, chcon[0], chcon)

                         warnings.resetwarnings()
                         paths = []
                         for i in cmds:
                                f = os.path.realpath(i)
                                if os.path.exists(f):
                                       paths.append(f)
                                else:
                                       paths.append(i)
                                       
                         copyfiles(newhomedir, newtmpdir, init_files + paths)
                         if X_ind:
                                xmodmapfile = newhomedir + "/.xmodmap"
                                xd = open(xmodmapfile,"w")
                                subprocess.Popen(["/usr/bin/xmodmap","-pke"],stdout=xd).wait()
                                xd.close()

                                execfile = newhomedir + "/.sandboxrc"
                                if session:
                                       setup_session(execfile)
                                else:
                                       setup_executable(execfile, " ".join(paths))

                                cmds =  ("/usr/sbin/seunshare -t %s -h %s -- %s /usr/share/sandbox/sandboxX.sh" % (newtmpdir, newhomedir, execcon)).split()
                                rc = os.spawnvp(os.P_WAIT, cmds[0], cmds)
                         else:
                                cmds =  ("/usr/sbin/seunshare -t %s -h %s -- %s " % (newtmpdir, newhomedir, execcon)).split()+cmds
                                rc = os.spawnvp(os.P_WAIT, cmds[0], cmds)
                         for i in paths:
                                if i not in X_FILES:
                                       continue
                                (dest, mtime) = X_FILES[i]
                                if os.path.getmtime(dest) > mtime:
                                       savefile(dest, i, X_ind)
                  else:
                         selinux.setexeccon(execcon)
                         rc = os.spawnvp(os.P_WAIT, cmds[0], cmds)
                         selinux.setexeccon(None)
           finally:
                  if home_and_temp:
                         if newhomedir and not existing_home:
                                shutil.rmtree(newhomedir)
                         if newtmpdir and not existing_temp:
                                shutil.rmtree(newtmpdir)
                  
    except getopt.GetoptError, error:
           usage(_("Options Error %s ") % error.msg)
    except OSError, error:
           error_exit(error.args[1])
    except ValueError, error:
           error_exit(error.args[0])
    except KeyError, error:
           error_exit(_("Invalid value %s") % error.args[0])
    except IOError, error:
           error_exit(error.message)
    except KeyboardInterrupt:
           rc = 0
           
    sys.exit(rc)
