diff options
Diffstat (limited to 'bin')
-rw-r--r-- | bin/Makefile | 11 | ||||
-rwxr-xr-x | bin/mkenvdir | 25 | ||||
-rwxr-xr-x | bin/rainbow-easy | 26 | ||||
-rwxr-xr-x | bin/rainbow-gc | 101 | ||||
-rwxr-xr-x | bin/rainbow-resume | 26 | ||||
-rwxr-xr-x | bin/rainbow-run | 142 | ||||
-rwxr-xr-x | bin/rainbow-sugarize | 90 | ||||
-rwxr-xr-x | bin/rainbow-xify | 76 |
8 files changed, 497 insertions, 0 deletions
diff --git a/bin/Makefile b/bin/Makefile new file mode 100644 index 0000000..143e1d1 --- /dev/null +++ b/bin/Makefile @@ -0,0 +1,11 @@ + +install: + install -D -m 0755 rainbow-run $(BINDIR)/rainbow-run + install -D -m 0755 rainbow-easy $(BINDIR)/rainbow-easy + install -D -m 0755 rainbow-resume $(BINDIR)/rainbow-resume + install -D -m 0755 rainbow-gc $(BINDIR)/rainbow-gc + install -D -m 0755 rainbow-sugarize $(BINDIR)/rainbow-sugarize + install -D -m 0755 rainbow-xify $(BINDIR)/rainbow-xify + install -D -m 0755 mkenvdir $(BINDIR)/mkenvdir + +.PHONY: install diff --git a/bin/mkenvdir b/bin/mkenvdir new file mode 100755 index 0000000..542400f --- /dev/null +++ b/bin/mkenvdir @@ -0,0 +1,25 @@ +#!/usr/bin/env python +import os, sys +from os.path import join, exists, isdir +from rainbow.util import make_dirs + +if len(sys.argv) < 2: + print sys.argv[0] + " DIR" + sys.exit(1) + +dir = sys.argv[1] + +if not exists(dir): + uid = os.geteuid() + gid = os.getegid() + make_dirs(dir, os.geteuid(), os.getegid(), 0755) + +def writable(path): + return os.access(path, os.W_OK) + +if exists(dir) and not (isdir(dir) and writable(dir)): + print 'DIR is not a writable directory.' + exit(1) + +for k,v in os.environ.iteritems(): + open(join(dir, k), 'w').write(v) diff --git a/bin/rainbow-easy b/bin/rainbow-easy new file mode 100755 index 0000000..b5a5c34 --- /dev/null +++ b/bin/rainbow-easy @@ -0,0 +1,26 @@ +#!/bin/bash + +function usage() { + echo "sudo $0 ID /path/to/program" + echo "ex: sudo $0 banking /bin/bash" + exit 1 +} + +if [ -z "$1" ] || [ -z "$SUDO_USER" ]; then usage || exit 1; fi + +ID="$1"; shift + +if [ -z "$DISPLAY" ]; then + DISPLAY=`python <<EOF +import os +from os.path import join + +ppid = os.getppid() +env = open(join('/proc', str(ppid),'environ')).read().split('\0') +for kv in env: + if kv.startswith("DISPLAY="): + print kv[len("DISPLAY="):] +EOF` +fi + +exec rainbow-run -s /var/spool/rainbow/2 -u "$SUDO_USER" -c "`pwd`" -f 0 -f 1 -f 2 -i "${SUDO_USER}_${ID}" -E "DISPLAY=$DISPLAY" -E "XAUTHORITY=${XAUTHORITY:-${HOME}/.Xauthority}" -a /usr/bin/rainbow-xify -o audio -o network -- "$@" diff --git a/bin/rainbow-gc b/bin/rainbow-gc new file mode 100755 index 0000000..82a5cd7 --- /dev/null +++ b/bin/rainbow-gc @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +import sys + +from subprocess import call, check_call, CalledProcessError +from os import listdir, unlink +from os.path import join, isdir, islink, exists +from optparse import OptionParser +from glob import glob + +from rainbow.util import trace, make_reporter + +def active_uid(uid): + cmd = ['/usr/bin/pgrep', '-U', uid] + ret = call(cmd, stdout=open('/dev/null')) + if ret in (0, 1): + return ret == 0 + raise CalledProcessError(ret, cmd) + +def sticky_uid(spool, uid): + return exists(join(spool, "sticky_uids", uid)) + +def gc_uid(log, spool, uid): + """This function conservatively attempts to garbage-collect stale uid + reservations. + """ + + # XXX: D-Bus caches passwd-db data! + # XXX: D-Bus uses a fixed 1k passwd buffer - be careful with long paths & + # comments + + reservation = join(spool, 'uid_pool', uid) + assert not isdir(reservation) and not islink(reservation) + + # XXX: We perform several execv()'s as root based on strings derived from + # this 'uid' parameter, which originates as a file-name in a user-writable + # directory. Better ideas for input validation would be welcome. + uid_num = int(uid) + assert uid_num >= 1000 and uid_num <= 65534 # XXX: magic numbers from util/spool.py + + if active_uid(uid) or sticky_uid(spool, uid): + log(1, "skipped uid %s", uid) + return + + for table in ('uid_to_instance_dir', 'uid_to_home_dir', 'uid_to_gid', 'uid_to_xephyr_auth', 'uid_to_xephyr_cookie', 'uid_to_xephyr_display'): + row = join(spool, table, uid) + # NB: it is important that rm -rf doesn't follow links. <MS> + cmd = ['/bin/rm', '-r', '-f', row] + log(2, "%s", ' '.join(cmd)) + check_call(cmd) + + for row in glob(join(spool, 'gid_to_members', '*', uid)): + # NB: it is important that rm -rf doesn't follow links. <MS> + cmd = ['/bin/rm', '-r', '-f', row] + log(2, "%s", ' '.join(cmd)) + check_call(cmd) + + # So long as we unlink the reservation last, we run no risk of seeing inconsistency + unlink(reservation) + log(1, "cleaned uid %s", uid) + +def gc_spool(log, spool): + ret = 0 + uspool = join(spool, 'uid_pool') + if exists(uspool) and isdir(uspool): + for maybe_uid in listdir(uspool): + try: gc_uid(log, spool, maybe_uid) + except KeyboardInterrupt: + raise + except: + trace() + ret = 1 + else: + log(1, "Skipping spool %s", spool) + ret = 1 + return ret + +def main(): + sys.excepthook = trace + + parser = OptionParser(version='0.1') + parser.add_option('-v', '--verbose', default=0, action='count', + help='Verbosity. Repeat for more verbose output.') + parser.add_option('-q', '--quiet', default=False, action='store_true', + help='Quiet. Disable all output.') + parser.add_option('-s', '--spool', default="/var/spool/rainbow/2", + help='Location of the rainbow spool.') + + opts, _ = parser.parse_args() + + report = make_reporter(opts.verbose, opts.quiet, sys.stdout) + + def check_spool(opts): + assert exists(opts.spool) and isdir(opts.spool) + return opts.spool + + spool = check_spool(opts) + return gc_spool(report, spool) + +if __name__ == "__main__": + exit(main()) diff --git a/bin/rainbow-resume b/bin/rainbow-resume new file mode 100755 index 0000000..2af756f --- /dev/null +++ b/bin/rainbow-resume @@ -0,0 +1,26 @@ +#!/bin/bash + +function usage() { + echo "sudo $0 RESUME_UID /path/to/program" + echo "ex: sudo $0 banking /bin/bash" + exit 1 +} + +if [ -z "$1" ] || [ -z "$SUDO_USER" ]; then usage || exit 1; fi + +RESUME_UID="$1"; shift + +if [ -z "$DISPLAY" ]; then + DISPLAY=`python <<EOF +import os +from os.path import join + +ppid = os.getppid() +env = open(join('/proc', str(ppid),'environ')).read().split('\0') +for kv in env: + if kv.startswith("DISPLAY="): + print kv[len("DISPLAY="):] +EOF` +fi + +exec rainbow-run -s /var/spool/rainbow/2 -u "$SUDO_USER" -c "`pwd`" -f 0 -f 1 -f 2 -E "DISPLAY=$DISPLAY" -E "XAUTHORITY=$XAUTHORITY" -a /usr/bin/rainbow-xify -o audio -o network -r "$RESUME_UID" -- "$@" diff --git a/bin/rainbow-run b/bin/rainbow-run new file mode 100755 index 0000000..3ce2590 --- /dev/null +++ b/bin/rainbow-run @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +import os +import sys +import pwd +import grp + +from stat import S_ISREG, ST_MODE +from os.path import join, isdir +from optparse import OptionParser +from pprint import pformat + +from rainbow.inject import inject +from rainbow.permissions import PermissionSet +from rainbow.util import make_reporter, trace, unshare, CLONE_NEWNS, read_envdir + +sys.excepthook = trace + +def main(): + parser = OptionParser(version='0.1') + parser.add_option('-v', '--verbose', default=0, action='count', + help='Verbosity. Repeat for more verbose output.') + parser.add_option('-q', '--quiet', default=False, action='store_true', + help='Quiet. Disable all output.') + parser.add_option('-s', '--spool', default=None, + help='Location of the rainbow spool.') + parser.add_option('-e', '--envdir', default=None, + help="Location of an envdir describing the desired environment.") + parser.add_option('-E', '--env', default=[], action='append', + help="Environment bindings to override.") + parser.add_option('-c', '--cwd', default=None, + help="Working directory.") + parser.add_option('-f', '--fd', default=[], action='append', + help="File descriptor number to leave open.") + parser.add_option('-i', '--id', default=[], action='append', + help="ID of shared-data group.") + parser.add_option('-o', '--option', default=[], action='append', + help="Options: video, audio, serial, constant-uid, xephyr, network.") + parser.add_option('-p', '--permissions', default=None, + help="Location of a permissions.info file.") + parser.add_option('-u', '--user', default=None, + help="Owning user.") + parser.add_option('-r', '--resume-user', default=None, + help="Resume <user>.") + parser.add_option('-a', '--assistant', default=None, + help="Task-specific assistant.") + parser.add_option('-G', '--group', default=[], action='append', + help="Extra groups.") + opts, args = parser.parse_args() + if len(args) == 0: + parser.print_help() + exit(1) + + report = make_reporter(opts.verbose, opts.quiet, sys.stdout) + + def check_spool(opts): + return opts.spool + + def check_env(opts): + return read_envdir(opts.envdir, opts.env) + + def check_argv(args): + return args + + def check_cwd(opts): + return opts.cwd + + def check_fds(opts): + return [int(s) for s in opts.fd] + + def check_owner(opts): + p = pwd.getpwnam(opts.user) + return p.pw_uid, p.pw_gid + + def check_groups(opts): + return [grp.getgrnam(g)[2] for g in opts.group] + + def check_xephyr(opts): + return 'xephyr' in opts.option + + def check_constant_uid(opts): + return 'constant-uid' in opts.option + + def check_audio(opts): + return 'audio' in opts.option + + def check_video(opts): + return 'video' in opts.option + + def check_serial(opts): + return 'serial' in opts.option + + def check_network(opts): + return 'network' in opts.option + + def check_resume_user(opts): + uid = None + if opts.resume_user: + uid = pwd.getpwnam(opts.resume_user).pw_uid + assert 10000 <= uid and uid < 60000 + return uid + + def check_data_ids(opts): + return opts.id + + def check_assistant(opts): + return opts.assistant + + uid, gid = check_owner(opts) + spool = check_spool(opts) + + # Use empty env? Use partial env? Fail unless perfect? + env = check_env(opts) + argv = check_argv(args) + cwd = check_cwd(opts) + + safe_fds = check_fds(opts) + + groups = check_groups(opts) + pset = PermissionSet(opts.permissions or []) + + # Dirty hack -- pass 'constant-uid' and 'strace' in as permissions. <MS> + for perm in ('constant-uid', 'audio', 'video', 'serial', 'network'): + pset._permissions.setdefault(perm, locals()['check_'+perm.replace('-','_')](opts)) + + data_ids = check_data_ids(opts) + assistant = check_assistant(opts) + xephyr = check_xephyr(opts) + + resume_uid = check_resume_user(opts) + if resume_uid: report(1, "resuming uid (%d)", resume_uid) + + args = (report, spool, env, argv, cwd, pset, safe_fds, uid, gid, resume_uid, groups, data_ids, assistant, xephyr) + report(1, 'rainbow:\n%s', pformat(args)) + + unshare(CLONE_NEWNS) + return inject(*args) + +if __name__ == '__main__': + main() + +# vim : et sw=4 ts=4 sts=4 : diff --git a/bin/rainbow-sugarize b/bin/rainbow-sugarize new file mode 100755 index 0000000..ea0e312 --- /dev/null +++ b/bin/rainbow-sugarize @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import sys +import pwd + +from os import getuid, getgid, environ, chmod, chown, symlink +from os.path import join, isdir, dirname +from optparse import OptionParser +from shutil import copyfile + +from rainbow.util import make_reporter, trace, make_dirs + +sys.excepthook = trace + +def main(): + parser = OptionParser(version='0.1') + parser.add_option('-v', '--verbose', default=0, action='count', + help='Verbosity. Repeat for more verbose output.') + parser.add_option('-q', '--quiet', default=False, action='store_true', + help='Quiet. Disable all output.') + parser.add_option('-u', '--user', default=None, + help="Isolated user.") + parser.add_option('-e', '--envdir', default=None, + help="Envdir to be used for launching.") + opts, args = parser.parse_args() + if not opts.user or not opts.envdir: + parser.print_help() + exit(1) + + report = make_reporter(opts.verbose, opts.quiet, sys.stdout) + + def check_user(report, opts): + report(1, 'Sugarizing isolated uid %s.', opts.user) + return opts.user + + def check_envdir(report, envdir): + report(1, 'Sugarizing envdir %s.', envdir) + assert isdir(envdir) + def write_envvar(k, v): + report(1, '-E %s=%s', k, v) + open(join(envdir, k), 'w').write(v) + return write_envvar + + user = check_user(report, opts) + write_envvar = check_envdir(report, opts.envdir) + + o = pwd.getpwuid(getuid()) + i = pwd.getpwnam(user) + h_o = o.pw_dir + h_i = i.pw_dir + + # We want to use xauth generate $DISPLAY . untrusted, but we don't have + # XSECURITY enabled. <MS> + #environ.setdefault('XAUTHORITY', join(h_o, '.Xauthority')) + environ.setdefault('ICEAUTHORITY', join(h_o, '.ICEauthority')) + + x_cookie_path = join(h_i, '.Xauthority') + make_dirs(dirname(x_cookie_path), getuid(), getgid(), 0777) + #copyfile(environ['XAUTHORITY'], x_cookie_path) + #chmod(x_cookie_path, 0666) + #chown(x_cookie_path, o.pw_uid, i.pw_gid) + + ice_cookie_path = join(h_i, '.ICEauthority') + make_dirs(dirname(ice_cookie_path), getuid(), getgid(), 0777) + copyfile(environ['ICEAUTHORITY'], ice_cookie_path) + chmod(ice_cookie_path, 0666) + chown(ice_cookie_path, o.pw_uid, i.pw_gid) + + for frag in ['owner.key.pub']: + path = join('.sugar/default/', frag) + make_dirs(dirname(join(h_i, path)), getuid(), getgid(), 0777) + copyfile(join(h_o, path), join(h_i, path)) + chmod(join(h_i, path), 0666) + + write_envvar('USER', i.pw_name) + write_envvar('HOME', h_i) + #write_envvar('XAUTHORITY', x_cookie_path) + write_envvar('ICEAUTHORITY', ice_cookie_path) + write_envvar('SUGAR_ACTIVITY_ROOT', h_i) + write_envvar('TMPDIR', join(h_i, 'tmp')) + write_envvar('DISPLAY', environ['DISPLAY']) + + symlink(".", join(h_i, "instance")) + symlink(environ["SUGAR_BUNDLE_ID"], join(h_i, "data")) + symlink(environ.get("TMPDIR", "/tmp"), join(h_i, "tmp")) + +if __name__ == '__main__': + main() + +# vim : et sw=4 ts=4 sts=4 : diff --git a/bin/rainbow-xify b/bin/rainbow-xify new file mode 100755 index 0000000..c51c00c --- /dev/null +++ b/bin/rainbow-xify @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import sys +import pwd + +from os import getuid, getgid, environ, chmod, chown +from os.path import join, isdir, dirname, exists +from optparse import OptionParser +from shutil import copyfile + +from rainbow.util import make_reporter, trace, make_dirs + +sys.excepthook = trace + +def main(): + parser = OptionParser(version='0.1') + parser.add_option('-v', '--verbose', default=0, action='count', + help='Verbosity. Repeat for more verbose output.') + parser.add_option('-q', '--quiet', default=False, action='store_true', + help='Quiet. Disable all output.') + parser.add_option('-u', '--user', default=None, + help="Isolated user.") + parser.add_option('-e', '--envdir', default=None, + help="Envdir to be used for launching.") + opts, args = parser.parse_args() + if not opts.user or not opts.envdir: + parser.print_help() + exit(1) + + report = make_reporter(opts.verbose, opts.quiet, sys.stdout) + + def check_user(report, opts): + report(1, 'X-ifying isolated uid %s.', opts.user) + return opts.user + + def check_envdir(report, envdir): + report(1, 'X-ifying envdir %s.', envdir) + assert isdir(envdir) + def write_envvar(k, v): + report(1, '-E %s=%s', k, v) + open(join(envdir, k), 'w').write(v) + return write_envvar + + user = check_user(report, opts) + write_envvar = check_envdir(report, opts.envdir) + + o = pwd.getpwuid(getuid()) + i = pwd.getpwnam(user) + h_o = o.pw_dir + h_i = i.pw_dir + + # XXX: Is it _always_ right to set these defaults? <MS> + environ.setdefault('XAUTHORITY', join(h_o, '.Xauthority')) + environ.setdefault('ICEAUTHORITY', join(h_o, '.ICEauthority')) + + for cookie in ['XAUTHORITY', 'ICEAUTHORITY']: + if exists(environ[cookie]): + cookie_path = join(h_i, cookie) + make_dirs(dirname(cookie_path), getuid(), getgid(), 0777) + copyfile(environ[cookie], cookie_path) + chmod(cookie_path, 0666) + chown(cookie_path, o.pw_uid, i.pw_gid) + write_envvar(cookie, cookie_path) + else: + report(1, "Cookie %s -> %s which doesn't exist.", cookie, environ[cookie]) + + write_envvar('USER', i.pw_name) + write_envvar('HOME', h_i) + #write_envvar('TMPDIR', join(h_i, 'tmp')) + if 'DISPLAY' in environ: + write_envvar('DISPLAY', environ['DISPLAY']) + +if __name__ == '__main__': + main() + +# vim : et sw=4 ts=4 sts=4 : |