initial commit of oz-ng

networking
brl 9 years ago
commit 6221d0595a

2
.gitignore vendored

@ -0,0 +1,2 @@
oz.iml
.idea/

@ -0,0 +1,45 @@
package oz
import (
"syscall"
"github.com/op/go-logging"
"os"
"os/signal"
)
func ReapChildProcs(log *logging.Logger, callback func(int, syscall.WaitStatus)) chan os.Signal {
sigs := make(chan os.Signal, 3)
signal.Notify(sigs, syscall.SIGCHLD)
go func() {
for {
<-sigs
handleSIGCHLD(log, callback)
}
}()
return sigs
}
func handleSIGCHLD(log *logging.Logger, callback func(int, syscall.WaitStatus)) {
var wstatus syscall.WaitStatus
for {
pid,err := syscall.Wait4(-1, &wstatus, syscall.WNOHANG, nil)
switch err {
case syscall.ECHILD:
return
case syscall.EINTR:
case nil:
if pid == 0 {
return
}
callback(pid, wstatus)
default:
if log != nil {
log.Warning("syscall.Wait4() returned error: %v", err)
}
return
}
}
}

@ -0,0 +1,8 @@
package main
import (
"github.com/subgraph/oz/oz-daemon"
)
func main() {
daemon.Main()
}

@ -0,0 +1,7 @@
package main
import "github.com/subgraph/oz/oz-init"
func main() {
ozinit.Main()
}

@ -0,0 +1,114 @@
package fs
import (
"io/ioutil"
"sort"
"strings"
"syscall"
"errors"
"github.com/op/go-logging"
)
func (fs *Filesystem) Cleanup() error {
if fs.base == "" {
msg := "cannot Cleanup() filesystem, fs.base is empty"
fs.log.Warning(msg)
return errors.New(msg)
}
for {
mnts,err := getMountsBelow(fs.base)
if err != nil {
return err
}
if len(mnts) == 0 {
return nil
}
atLeastOne, lastErr := mnts.unmountAll(fs.log)
if !atLeastOne {
return lastErr
}
}
}
func (mnts mountEntries) unmountAll(log *logging.Logger) (bool, error) {
reterr := error(nil)
atLeastOne := false
for _,m := range mnts {
if err := syscall.Unmount(m.dir, 0); err != nil {
log.Warning("Failed to unmount mountpoint %s: %v", m.dir, err)
reterr = err
} else {
atLeastOne = true
}
}
return atLeastOne, reterr
}
type mountEntry struct {
src string
dir string
fs string
options string
}
type mountEntries []*mountEntry
func (m mountEntries) Len() int { return len(m) }
func (m mountEntries) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m mountEntries) Less(i, j int) bool { return m[i].depth() > m[j].depth() }
func (me mountEntry) depth() int { return strings.Count(me.dir, "/") }
func getMountsBelow(base string) (mountEntries, error) {
mnts, err := getProcMounts()
if err != nil {
return nil, err
}
sort.Sort(mnts)
var filtered mountEntries
for _, m := range mnts {
if strings.HasPrefix(m.dir, base) {
filtered = append(filtered, m)
}
}
return filtered, nil
}
func (m mountEntries) contains(dir string) bool {
for _, mnt := range m {
if dir == mnt.dir {
return true
}
}
return false
}
func getProcMounts() (mountEntries, error) {
lines, err := readProcMounts()
if err != nil {
return nil, err
}
var entries mountEntries
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) >= 4 {
entries = append(entries, &mountEntry{
src: parts[0],
dir: parts[1],
fs: parts[2],
options: parts[3],
})
}
}
return entries, nil
}
func readProcMounts() ([]string, error) {
content, err := ioutil.ReadFile("/proc/mounts")
if err != nil {
return nil, err
}
return strings.Split(string(content), "\n"), nil
}

@ -0,0 +1,207 @@
package fs
import (
"fmt"
"os"
"os/user"
"path"
"strconv"
"strings"
"syscall"
"github.com/op/go-logging"
"github.com/subgraph/oz"
)
type directory struct {
path string
empty bool
}
type Filesystem struct {
log *logging.Logger
home string
base string
root string
userID string
noDefaults bool
noSysAndProc bool
whitelist []*mountItem
blacklist []*mountItem
}
func (fs *Filesystem) Root() string {
return fs.root
}
func (fs *Filesystem) addWhitelist(path, target string, readonly bool) error {
item, err := fs.newItem(path, target, readonly)
if err != nil {
return err
}
fs.whitelist = append(fs.whitelist, item)
return nil
}
func (fs *Filesystem) addBlacklist(path string) error {
item, err := fs.newItem(path, "", false)
if err != nil {
return err
}
fs.blacklist = append(fs.blacklist, item)
return nil
}
func (fs *Filesystem) newItem(path, target string, readonly bool) (*mountItem, error) {
p, err := fs.resolveVars(path)
if err != nil {
return nil, err
}
return &mountItem{
path: p,
target: target,
//readonly: readonly,
fs: fs,
}, nil
}
func NewFromProfile(profile *oz.Profile, log *logging.Logger) *Filesystem {
fs := NewFilesystem(profile.Name, log)
for _,wl := range profile.Whitelist {
fs.addWhitelist(wl.Path, wl.Path, wl.ReadOnly)
}
for _,bl := range profile.Blacklist {
fs.addBlacklist(bl.Path)
}
fs.noDefaults = profile.NoDefaults
fs.noSysAndProc = profile.NoSysProc
return fs
}
func NewFilesystem(name string, log *logging.Logger) *Filesystem {
fs := new(Filesystem)
fs.log = log
if log == nil {
fs.log = logging.MustGetLogger("oz")
}
fs.base = path.Join("/srv/oz", name)
fs.root = path.Join(fs.base, "rootfs")
u, err := user.Current()
if err != nil {
panic("Failed to look up current user: " + err.Error())
}
fs.home = u.HomeDir
fs.userID = strconv.Itoa(os.Getuid())
return fs
}
/*
func xcreateEmptyDirectories(base string, paths []string) error {
for _, p := range paths {
target := path.Join(base, p)
if err := createEmptyDir(p, target); err != nil {
return err
}
}
return nil
}
func createEmptyDir(source, target string) error {
return nil
}
func createSubdirs(base string, subdirs []string) error {
for _, sdir := range subdirs {
path := path.Join(base, sdir)
if err := createDirTree(path); err != nil {
return err
}
}
return nil
}
func createDirTree(path string) error {
st, err := os.Stat(path)
if err == nil {
if !st.IsDir() {
return fmt.Errorf("cannot create directory %s because path already exists and is not directory", path)
}
return nil
}
if !os.IsNotExist(err) {
return fmt.Errorf("unexpected error attempting Stat() on path %s: %v", path, err)
}
if err := os.MkdirAll(path, 0755); err != nil {
return fmt.Errorf("error creating directory tree %s: %v", path, err)
}
return nil
}
*/
// bindMount performs a bind mount of the source path item so that it is visible
// at the target path. By default the mount is flagged MS_NOSUID and MS_NODEV
// but additional flags can be passed in extraFlags. If the readonly flag is
// set the bind mount is remounted as MS_RDONLY.
func bindMount(source, target string, readonly bool, extraFlags uintptr) error {
flags := syscall.MS_BIND | syscall.MS_NOSUID | syscall.MS_NODEV | extraFlags
if err := syscall.Mount(source, target, "", flags, ""); err != nil {
return fmt.Errorf("failed to bind mount %s to %s: %v", source, target, err)
}
if readonly {
flags |= syscall.MS_RDONLY | syscall.MS_REMOUNT
if err := syscall.Mount("", target, "", flags, ""); err != nil {
return fmt.Errorf("failed to remount %s as RDONLY: %v", target, err)
}
}
return nil
}
func createEmptyFile(name string, mode os.FileMode) error {
if err := os.MkdirAll(path.Dir(name), 0750); err != nil {
return err
}
fd, err := os.Create(name)
if err != nil {
return err
}
if err := fd.Close(); err != nil {
return err
}
if err := os.Chmod(name, mode); err != nil {
return err
}
return nil
}
func copyPathPermissions(root, src string) error {
current := "/"
for _, part := range strings.Split(src, "/") {
if part == "" {
continue
}
current = path.Join(current, part)
target := path.Join(root, current)
if err := copyFilePermissions(current, target); err != nil {
return err
}
}
return nil
}
func copyFilePermissions(src, target string) error {
fi, err := os.Stat(src)
if err != nil {
return err
}
return copyFileInfo(fi, target)
}
func copyFileInfo(info os.FileInfo, target string) error {
st := info.Sys().(*syscall.Stat_t)
os.Chown(target, int(st.Uid), int(st.Gid))
os.Chmod(target, info.Mode().Perm())
return nil
}

@ -0,0 +1,183 @@
package fs
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"github.com/op/go-logging"
)
type MountFlag int
func (mf MountFlag) isSet(f MountFlag) bool {
return mf&f == f
}
const (
MountReadOnly MountFlag = 1 << iota
MountCreateIfAbsent
)
type mountItem struct {
path string
target string
flags MountFlag
fs *Filesystem
}
func (mi *mountItem) targetPath() string {
root := mi.fs.root
if mi.target != "" {
return path.Join(root, mi.target)
}
return path.Join(root, mi.path)
}
func (mi *mountItem) bind() error {
if strings.Contains(mi.path, "*") {
return mi.bindGlobbed()
}
return mi.bindItem()
}
func (mi *mountItem) bindGlobbed() error {
if mi.target != "" {
mi.fs.log.Warning("Ignoring target directory (%s) for mount item containing glob character: (%s)", mi.target, mi.path)
mi.target = ""
}
globbed, err := filepath.Glob(mi.path)
if err != nil {
return err
}
savedPath := mi.path
for _, p := range globbed {
if strings.Contains(p, "*") {
// XXX
continue
}
mi.path = p
if err := mi.bind(); err != nil {
// XXX
mi.path = savedPath
return err
}
}
mi.path = savedPath
return nil
}
func (mi *mountItem) readSourceInfo(src string) (os.FileInfo, error) {
if fi, err := os.Stat(src); err == nil {
return fi, nil
} else if !os.IsNotExist(err) {
return nil, err
}
if !mi.flags.isSet(MountCreateIfAbsent) {
return nil, fmt.Errorf("source path (%s) does not exist", src)
}
if !strings.HasPrefix(src, mi.fs.home) {
return nil, fmt.Errorf("mount item (%s) has flag MountCreateIfAbsent, but is not child of home directory (%s)", src, mi.fs.home)
}
if err := os.MkdirAll(src, 0750); err != nil {
return nil, err
}
pinfo, err := os.Stat(path.Dir(src))
if err != nil {
return nil, err
}
if err := copyFileInfo(pinfo, src); err != nil {
return nil, err
}
return os.Stat(src)
}
func (mi *mountItem) bindItem() error {
src, err := filepath.EvalSymlinks(mi.path)
if err != nil {
return fmt.Errorf("error resolving symlinks for path (%s): %v", mi.path, err)
}
sinfo, err := mi.readSourceInfo(src)
if err != nil {
// XXX
return err
}
target := mi.targetPath()
_, err = os.Stat(target)
if err == nil || !os.IsNotExist(err) {
mi.fs.log.Warning("Target (%s) already exists, ignoring", target)
return nil
}
if sinfo.IsDir() {
if err := os.MkdirAll(target, sinfo.Mode().Perm()); err != nil {
return err
}
} else {
if err := createEmptyFile(target, 0750); err != nil {
return err
}
}
if err := copyPathPermissions(mi.fs.root, src); err != nil {
return fmt.Errorf("failed to copy path permissions for (%s): %v", src, err)
}
return bindMount(src, target, mi.flags.isSet(MountReadOnly), 0)
}
func (mi *mountItem) blacklist() error {
if strings.Contains(mi.path, "*") {
return mi.blacklistGlobbed()
}
return blacklistItem(mi.path, mi.fs.log)
}
func (mi *mountItem) blacklistGlobbed() error {
globbed, err := filepath.Glob(mi.path)
if err != nil {
// XXX
}
for _, p := range globbed {
if err := blacklistItem(p, mi.fs.log); err != nil {
return err
}
}
return nil
}
func blacklistItem(path string, log *logging.Logger) error {
p, err := filepath.EvalSymlinks(path)
if err != nil {
log.Warning("Symlink evaluation failed for path: %s", path)
return err
}
fi, err := os.Stat(p)
if err != nil {
if os.IsNotExist(err) {
log.Info("Blacklist item (%s) does not exist", p)
return nil
}
return err
}
src := emptyFilePath
if fi.IsDir() {
src = emptyDirPath
}
if err := syscall.Mount(src, p, "none", syscall.MS_BIND, "mode=400,gid=0"); err != nil {
// XXX warning
return err
}
// XXX log success
return nil
}

@ -0,0 +1,134 @@
package fs
import (
"os"
"path"
"syscall"
)
// OzInit is run from the oz-init process and performs post chroot filesystem initialization
func (fs *Filesystem) OzInit() error {
if err := fs.ozinitMountDev(); err != nil {
return err
}
if err := fs.ozinitMountSysProc(); err != nil {
return err
}
if err := fs.ozinitCreateSymlinks(); err != nil {
return err
}
if err := fs.ozinitBlacklistItems(); err != nil {
return err
}
return nil
}
func (fs *Filesystem) ozinitMountDev() error {
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC)
if err := syscall.Mount("none", "/dev", "devtmpfs", flags, ""); err != nil {
fs.log.Warning("Failed to mount devtmpfs: %v", err)
return err
}
if err := mountSpecial("/dev/shm", "tmpfs"); err != nil {
fs.log.Warning("Failed to mount shm directory: %v", err)
return err
}
if err := mountSpecial("/dev/pts", "devpts"); err != nil {
fs.log.Warning("Failed to mount pts directory: %v", err)
return err
}
return nil
}
func mountSpecial(path, mtype string) error {
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC)
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
return syscall.Mount(path, path, mtype, flags, "")
}
func (fs *Filesystem) ozinitMountSysProc() error {
if fs.noSysAndProc {
return nil
}
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC | syscall.MS_NODEV)
proc := "/proc"
if err := syscall.Mount("proc", proc, "proc", flags, ""); err != nil {
fs.log.Warning("Failed to mount /proc: %v", err)
return err
}
roMounts := []string{
"sysrq-trigger",
"bus",
"irq",
"sys/kernel/hotplug",
}
for _, rom := range roMounts {
p := path.Join(proc, rom)
if err := bindMount(p, p, true, 0); err != nil {
fs.log.Warning("Failed to RO mount %s: %v", p, err)
return err
}
}
if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY|flags, ""); err != nil {
fs.log.Warning("Failed to mount /sys: %v", err)
return err
}
return nil
}
func (fs *Filesystem) ozinitCreateSymlinks() error {
for _, sl := range basicSymlinks {
if err := syscall.Symlink(sl[0], sl[1]); err != nil {
return err
}
}
return nil
}
func (fs *Filesystem) ozinitBlacklistItems() error {
if err := createBlacklistDir(emptyDirPath); err != nil {
return err
}
if err := createBlacklistFile(emptyFilePath); err != nil {
return err
}
for _, item := range fs.blacklist {
if err := item.blacklist(); err != nil {
return err
}
}
return nil
}
func createBlacklistDir(path string) error {
if err := os.MkdirAll(path, 0000); err != nil {
return err
}
return setBlacklistPerms(path, 0500)
}
func createBlacklistFile(path string) error {
fd, err := os.Create(path)
if err != nil {
return err
}
if err := fd.Close(); err != nil {
return err
}
return setBlacklistPerms(path, 0400)
}
func setBlacklistPerms(path string, mode os.FileMode) error {
if err := os.Chown(path, 0, 0); err != nil {
return err
}
if err := os.Chmod(path, mode); err != nil {
return err
}
return nil
}

@ -0,0 +1,50 @@
package fs
import (
"fmt"
"os/exec"
"path"
"path/filepath"
"strings"
)
func (fs *Filesystem) resolvePath(p string) ([]string, error) {
p, err := fs.resolveVars(p)
if err != nil {
return nil, err
}
return fs.resolveGlob(p)
}
func (fs *Filesystem) resolveVars(p string) (string, error) {
const pathVar = "${PATH}/"
const homeVar = "${HOME}"
const uidVar = "${UID}"
switch {
case strings.HasPrefix(p, pathVar):
resolved, err := exec.LookPath(p[len(pathVar):])
if err != nil {
return "", fmt.Errorf("failed to resolve %s", p)
}
return resolved, nil
case strings.HasPrefix(p, homeVar):
return path.Join(fs.home, p[len(homeVar):]), nil
case strings.HasPrefix(p, uidVar):
return strings.Replace(p, uidVar, fs.userID, -1), nil
}
return p, nil
}
func (fs *Filesystem) resolveGlob(p string) ([]string, error) {
if !strings.Contains(p, "*") {
return []string{p}, nil
}
list, err := filepath.Glob(p)
if err != nil {
return nil, fmt.Errorf("failed to glob resolve %s: %v", p, err)
}
return list, nil
}

@ -0,0 +1,144 @@
package fs
import (
"fmt"
"os"
"path"
"syscall"
)
var basicBindDirs = []string{
"/bin", "/lib", "/lib64", "/usr", "/etc", "/var/lib/oz",
}
var basicEmptyDirs = []string{
"/sbin", "/var", "/var/lib",
"/var/cache", "/home", "/boot",
"/tmp", "/run", "/run/user",
"/run/lock", "/root", "/opt",
"/srv", "/dev", "/proc", "/sys",
"/mnt", "/media",
}
var basicBlacklist = []string{
"/usr/sbin", "/sbin", "${PATH}/su",
"${PATH}/sudo", "${PATH}/fusermount",
"${PATH}/xinput", "${PATH}/strace",
"${PATH}/mount", "${PATH}/umount",
}
const emptyFilePath = "/tmp/oz.ro.file"
const emptyDirPath = "/tmp/oz.ro.dir"
var basicSymlinks = [][2]string{
{"/run", "/var/run"},
{"/tmp", "/var/tmp"},
{"/run/lock", "/var/lock"},
}
func (fs *Filesystem) Setup() error {
if err := fs.setupRootfs(); err != nil {
return err
}
if err := fs.setupChroot(); err != nil {
return err
}
return fs.setupMountItems()
}
func (fs *Filesystem) setupRootfs() error {
if err := os.MkdirAll(fs.base, 0755); err != nil {
return fmt.Errorf("unable to create directory (%s): %v", fs.base, err)
}
flags := uintptr(syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV)
data := "mode=755,gid=0"
if err := syscall.Mount(fs.base, fs.base, "tmpfs", flags, data); err != nil {
return fmt.Errorf("failed to create base tmpfs at %s: %v", fs.base, err)
}
// create extra directories
extra := []string{"sockets"}
for _, sub := range extra {
d := path.Join(fs.base, sub)
if err := os.Mkdir(d, 0755); err != nil {
return fmt.Errorf("unable to create directory (%s): %v", d, err)
}
}
return nil
}
func (fs *Filesystem) setupChroot() error {
var err error
if fs.noDefaults {
err = createEmptyDirectories(fs.root, basicBindDirs)
} else {
err = bindBasicDirectories(fs.root, basicBindDirs)
}
if err != nil {
return err
}
err = createEmptyDirectories(fs.root, basicEmptyDirs)
if err != nil {
return err
}
return setupTmp(fs.root)
}
func bindBasicDirectories(root string, dirs []string) error {
for _, src := range dirs {
st, err := os.Lstat(src)
if err != nil {
return err
}
mode := st.Mode()
target := path.Join(root, src)
if err := os.MkdirAll(target, mode.Perm()); err != nil {
return err
}
if err := bindMount(src, target, true, 0); err != nil {
return err
}
}
return nil
}
func createEmptyDirectories(root string, dirs []string) error {
for _, p := range dirs {
target := path.Join(root, p)
if err := createEmptyDirectory(p, target); err != nil {
return err
}
}
return nil
}
func createEmptyDirectory(source, target string) error {
fi, err := os.Stat(source)
if err != nil {
return err
}
mode := fi.Mode()
if err := os.MkdirAll(target, mode.Perm()); err != nil {
return err
}
if err := copyFileInfo(fi, target); err != nil {
return err
}
return nil
}
func setupTmp(root string) error {
target := path.Join(root, "tmp")
if err := os.Chmod(target, 0777); err != nil {
return err
}
return bindMount(target, target, false, syscall.MS_NOEXEC)
}
func (fs *Filesystem) setupMountItems() error {
for _, item := range fs.whitelist {
if err := item.bind(); err != nil {
// XXX
}
}
return nil
}

@ -0,0 +1,82 @@
package ipc
import (
"reflect"
"errors"
"fmt"
)
type handlerMap map[string]reflect.Value
func (handlers handlerMap) dispatch(m *Message) error {
h,ok := handlers[m.Type]
if !ok {
return errors.New("no handler found for message type:"+ m.Type)
}
return executeHandler(h, m)
}
func executeHandler(h reflect.Value, m *Message) error {
var args [2]reflect.Value
args[0] = reflect.ValueOf(m.Body)
args[1]= reflect.ValueOf(m)
rs := h.Call(args[:])
if len(rs) != 1 {
return errors.New("handler function did not return expected single result value")
}
if rs[0].IsNil() {
return nil
}
return rs[0].Interface().(error)
}
func (handlers handlerMap) addHandler(h interface{}) error {
msgType, err := typeCheckHandler(h)
if err != nil {
return err
}
if _,ok := handlers[msgType]; ok{
return fmt.Errorf("duplicate handler registered for message type '%s'", msgType)
}
handlers[msgType] = reflect.ValueOf(h)
return nil
}
var errType = reflect.TypeOf((*error)(nil)).Elem()
var messageType = reflect.TypeOf((*Message)(nil))
func typeCheckHandler(h interface{}) (string, error) {
t := reflect.TypeOf(h)
if t.Kind() != reflect.Func {
return "", fmt.Errorf("handler %v is not a function", t)
}
if t.NumIn() != 2 {
return "", fmt.Errorf("handler %v has incorrect number of input arguments, got %d", t, t.NumIn())
}
if t.NumOut() != 1 {
return "", fmt.Errorf("handler %v has incorrect number of return values %d", t, t.NumOut())
}
if t.In(0).Kind() != reflect.Ptr {
return "", errors.New("first argument of handler is not a pointer")
}
in0 := t.In(0).Elem()
if in0.Kind() != reflect.Struct {
return "", fmt.Errorf("first argument of handler is not a pointer to struct")
}
if in1 := t.In(1); !in1.AssignableTo(messageType) {
return "", fmt.Errorf("second argument of handler must have type *Message")
}
if out := t.Out(0); !out.AssignableTo(errType) {
return "", fmt.Errorf("return type of handler must be error")
}
if in0.NumField() == 0 {
return "", fmt.Errorf("first argument structure has no fields")
}
if len(in0.Field(0).Tag) == 0 {
return "", fmt.Errorf("first argument structure, first field has no tag")
}
return string(in0.Field(0).Tag), nil
}

@ -0,0 +1,86 @@
package ipc
import (
"testing"
"reflect"
"errors"
)
func TestTypeCheckHandler(t *testing.T) {
type testStruct struct {}
cases := []interface{} {
"foo",
func(){},
func(a,b int){},
func(a *testStruct, b *Message) {},
func(a,b *int) error{return nil},
func(a *testStruct, b int) error {return nil},
func(a *testStruct, b Message) error {return nil},
func(a *testStruct, b *Message) int{return 0},
func(a *testStruct, b *Message, c int) error {return nil},
}
for i,h := range cases {
if _,err := typeCheckHandler(h); err == nil {
t.Errorf("typeCheckHandler should return an error for case %d", i)
}
}
}
func TestAddHandler(t *testing.T) {
hmap := handlerMap(make(map[string]reflect.Value))
type testStruct struct{
t int "tst"
}
legit := func(ts *testStruct, m *Message) error { return nil }
if err := hmap.addHandler("bar"); err == nil {
t.Error("attempt to register string as handler function did not fail as expected")
}
if err := hmap.addHandler(legit); err != nil {
t.Error("registration of good handler function failed:", err)
}
if err := hmap.addHandler(legit); err == nil {
t.Error("registration of duplicate handler function did not fail")
}
}
func TestDispatch(t *testing.T) {
type testStruct struct{ t int "tester"}
type testStruct2 struct{ t int "tester2"}
count := 0
h1 := func(ts *testStruct, m *Message) error {
count += 1
return nil
}
h2 := func(ts *testStruct2, m *Message) error {
count += 1
return errors.New("...")
}
hmap := handlerMap(make(map[string]reflect.Value))
if err := hmap.addHandler(h1); err != nil {
t.Errorf("unexpected failure to register handler: %v", err)
}
if err := hmap.addHandler(h2); err != nil {
t.Errorf("unexpected failure to register handler: %v", err)
}
m := new(Message)
m.Type = "tester"
m.Body = new(testStruct)
if err := hmap.dispatch(m); err != nil {
t.Error("unexpected error calling dispatch():", err)
}
m.Type = "tester2"
m.Body = new(testStruct2)
if err := hmap.dispatch(m); err == nil {
t.Errorf("dispatch() did not return error as expected")
}
if count != 2 {
t.Errorf("count was not incremented to 2 as expected. count = %d", count)
}
}

@ -0,0 +1,288 @@
package ipc
import (
"encoding/json"
"errors"
"net"
"syscall"
"github.com/op/go-logging"
"reflect"
"crypto/rand"
"encoding/hex"
"fmt"
)
const maxFdCount = 3
var log = logging.MustGetLogger("oz")
type MsgConn struct {
msgs chan *Message
addr *net.UnixAddr
conn *net.UnixConn
buf [1024]byte
oob []byte
handlers handlerMap
factory MsgFactory
isClosed bool
done chan bool
idGen <-chan int
respMan *responseManager
}
func NewMsgConn(factory MsgFactory, address string) *MsgConn {
mc := new(MsgConn)
mc.addr = &net.UnixAddr{address, "unixgram"}
mc.oob = createOobBuffer()
mc.msgs = make(chan *Message)
mc.handlers = make(map[string]reflect.Value)
mc.factory = factory
mc.done = make(chan bool)
mc.idGen = newIdGen(mc.done)
mc.respMan = newResponseManager()
return mc
}
func newIdGen(done <-chan bool) <-chan int {
ch := make(chan int)
go idGenLoop(done, ch)
return ch
}
func idGenLoop(done <-chan bool, out chan <- int) {
current := int(1)
for {
select {
case out <- current:
current += 1
case <-done:
return
}
}
}
func (mc *MsgConn) Listen() error {
if mc.conn != nil {
return errors.New("cannot Listen(), already connected")
}
conn, err := net.ListenUnixgram("unixgram", mc.addr)
if err != nil {
return err
}
if err := setPassCred(conn); err != nil {
return err
}
mc.conn = conn
return nil
}
func (mc *MsgConn) Connect() error {
if mc.conn != nil {
return errors.New("cannot Connect(), already connected")
}
clientAddr,err := CreateRandomAddress("@oz-")
if err != nil {
return err
}
conn, err := net.DialUnix("unixgram", &net.UnixAddr{clientAddr, "unixgram"}, nil)
if err != nil {
return err
}
mc.conn = conn
go mc.readLoop()
return nil
}
func CreateRandomAddress(prefix string) (string,error) {
var bs [16]byte
n,err := rand.Read(bs[:])
if n != len(bs) {
return "", errors.New("incomplete read of random bytes for client name")
}
if err != nil {
return "", errors.New("error reading random bytes for client name: "+ err.Error())
}
return prefix+ hex.EncodeToString(bs[:]),nil
}
func (mc *MsgConn) Run() error {
go mc.readLoop()
for m := range mc.msgs {
if err := mc.handlers.dispatch(m); err != nil {
return fmt.Errorf("error dispatching message: %v", err)
}
}
return nil
}
func (mc *MsgConn) readLoop() {
for {
if mc.processOneMessage() {
return
}
}
}
func (mc *MsgConn) processOneMessage() bool {
m,err := mc.readMessage()
if err != nil {
close(mc.msgs)
if !mc.isClosed {
log.Warning("error on MsgConn.readMessage(): %v", err)
}
return true
}
if !mc.respMan.handle(m) {
mc.msgs <- m
}
return false
}
func (mc *MsgConn) Close() error {
mc.isClosed = true
close(mc.done)
return mc.conn.Close()
}
func createOobBuffer() []byte {
oobSize := syscall.CmsgSpace(syscall.SizeofUcred) + syscall.CmsgSpace(4*maxFdCount)
return make([]byte, oobSize)
}
func (mc *MsgConn) readMessage() (*Message, error) {
n, oobn, _, a, err := mc.conn.ReadMsgUnix(mc.buf[:], mc.oob)
if err != nil {
return nil, err
}
m, err := mc.parseMessage(mc.buf[:n])
if err != nil {
return nil, err
}
m.mconn = mc
m.Peer = a
if oobn > 0 {
err := m.parseControlData(mc.oob[:oobn])
if err != nil {
}
}
return m, nil
}
// AddHandlers registers a list of message handling functions with a MsgConn instance.
// Each handler function must have two arguments and return a single error value. The
// first argument must be pointer to a message structure type. A message structure type
// is a structure that must have a struct tag on the first field:
//
// type FooMsg struct {
// Stuff string "Foo" // <------ struct tag
// // etc...
// }
//
// type SimpleMsg struct {
// dummy int "Simple" // struct has no fields, so add an unexported dummy field just for the tag
// }
//
// The second argument to a handler function must have type *ipc.Message. After a handler function
// has been registered, received messages matching the first argument will be dispatched to the corresponding
// handler function.
//
// func fooHandler(foo *FooMsg, msg *ipc.Message) error { /* ... */ }
// func simpleHandler(simple *SimpleMsg, msg *ipc.Message) error { /* ... */ }
//
// /* register fooHandler() to handle incoming FooMsg and SimpleHandler to handle SimpleMsg */
// conn.AddHandlers(fooHandler, simpleHandler)
//
func (mc *MsgConn) AddHandlers(args ...interface{}) error {
for len(args) > 0 {
if err := mc.handlers.addHandler(args[0]); err != nil {
return err
}
args = args[1:]
}
return nil
}
func (mc *MsgConn) SendMsg(msg interface{}, fds... int) error {
return mc.sendMessage(msg, <-mc.idGen, mc.addr, fds...)
}
func (mc *MsgConn) ExchangeMsg(msg interface{}, fds... int) (ResponseReader, error) {
id := <-mc.idGen
rr := mc.respMan.register(id)
if err := mc.sendMessage(msg, id, mc.addr, fds...); err != nil {
rr.Done()
return nil, err
}
return rr,nil
}
func (mc *MsgConn) sendMessage(msg interface{}, msgID int, dst *net.UnixAddr, fds... int) error {
msgType, err := getMessageType(msg)
if err != nil {
return err
}
base, err := mc.newBaseMessage(msgType, msgID, msg)
if err != nil {
return err
}
raw, err := json.Marshal(base)
if err != nil {
return err
}
return mc.sendRaw(raw, dst, fds...)
}
func getMessageType(msg interface{}) (string, error) {
t := reflect.TypeOf(msg)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return "", fmt.Errorf("sendMessage() msg (%T) is not a struct", msg)
}
if t.NumField() == 0 || len(t.Field(0).Tag) == 0 {
return "", fmt.Errorf("sendMessage() msg struct (%T) does not have tag on first field")
}
return string(t.Field(0).Tag), nil
}
func (mc *MsgConn) newBaseMessage(msgType string, msgID int, body interface{}) (*BaseMsg, error) {
bodyBytes,err := json.Marshal(body)
if err != nil {
return nil, err
}
base := new(BaseMsg)
base.Type = msgType
base.MsgID = msgID
base.Body = bodyBytes
return base, nil
}
func (mc *MsgConn) sendRaw(data []byte, dst *net.UnixAddr, fds ...int) error {
if len(fds) > 0 {
return mc.sendWithFds(data, dst, fds)
}
return mc.write(data, dst)
}
func (mc *MsgConn) write(data []byte, dst *net.UnixAddr) error {
if dst != nil {
_,err := mc.conn.WriteToUnix(data, dst)
return err
}
_,err := mc.conn.Write(data)
return err
}
func (mc *MsgConn) sendWithFds(data []byte, dst *net.UnixAddr, fds []int) error {
oob := syscall.UnixRights(fds...)
_,_,err := mc.conn.WriteMsgUnix(data, oob, dst)
return err
}

@ -0,0 +1,100 @@
package ipc
import (
"testing"
"sync"
"os"
)
type TestMsg struct {
t int "Test"
}
type testConnection struct {
server *MsgConn
client *MsgConn
wg sync.WaitGroup
called bool
}
type testServer struct {
conn *MsgConn
wg sync.WaitGroup
}
const testSocket = "@test"
var testFactory = NewMsgFactory(new(TestMsg))
func testConnect(handler func(*TestMsg, *Message) error) (*testConnection, error) {
s := NewMsgConn(testFactory, testSocket)
c := NewMsgConn(testFactory, testSocket)
tc := &testConnection{
server: s,
client: c,
}
wrapper := func(tm *TestMsg, msg *Message) error {
err := handler(tm, msg)
tc.called = true
tc.wg.Done()
return err
}
if err := s.AddHandlers(wrapper); err != nil {
return nil, err
}
if err := s.Listen(); err != nil {
return nil, err
}
if err := c.Connect(); err != nil {
return nil, err
}
tc.wg.Add(1)
go tc.server.Run()
return tc, nil
}
func runTest(t *testing.T, handler func(*TestMsg, *Message) error, tester func(*testConnection)) {
tc,err := testConnect(handler)
if err != nil {
t.Error("error setting up test connection:", err)
}
tester(tc)
tc.wait()
if !tc.called {
t.Error("handler function not called")
}
}
func (tc *testConnection) wait() {
tc.wg.Wait()
tc.client.Close()
tc.server.Close()
}
func TestUcred(t *testing.T) {
handler := func(tm *TestMsg, msg *Message) error {
uid := uint32(os.Getuid())
gid := uint32(os.Getgid())
pid := int32(os.Getpid())
u := msg.Ucred
if u.Uid != uid || u.Gid != gid || u.Pid != pid {
t.Errorf("ucred (%d/%d/%d) does not match process (%d/%d/%d)", u.Uid, u.Gid, u.Pid, uid, gid, pid)
}
return nil
}
runTest(t, handler, func(tc *testConnection) {
tc.client.SendMsg(&TestMsg{})
})
}
func TestPassFDs(t *testing.T) {
fds := []int{1,2}
handler := func(tm *TestMsg, msg *Message) error {
if len(msg.Fds) != len(fds) {
t.Errorf("Expecting %d descriptors, got %d", len(fds), len(msg.Fds))
}
return nil
}
runTest(t, handler, func(tc *testConnection) {
tc.client.SendMsg(&TestMsg{}, fds...)
})
}

@ -0,0 +1,124 @@
package ipc
import (
"encoding/json"
"net"
"syscall"
"fmt"
"reflect"
"errors"
)
func NewMsgFactory(msgTypes ...interface{}) MsgFactory {
mf := (MsgFactory)(make(map[string]func() interface{}))
for _, mt := range msgTypes {
if err := mf.register(mt); err != nil {
log.Fatalf("failed adding (%T) in NewMsgFactory: %v", mt, err)
return nil
}
}
return mf
}
type MsgFactory map[string](func() interface{})
func (mf MsgFactory) create(msgType string) (interface{}, error) {
f,ok := mf[msgType]
if !ok {
return nil, fmt.Errorf("cannot create msg type: %s", msgType)
}
return f(), nil
}
func (mf MsgFactory) register(mt interface{}) error {
t := reflect.TypeOf(mt)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return errors.New("not a structure")
}
if t.NumField() == 0 || len(t.Field(0).Tag) == 0 {
return errors.New("no tag on first field of structure")
}
tag := string(t.Field(0).Tag)
mf[tag] = func() interface{} {
v := reflect.New(t)
return v.Interface()
}
return nil
}
type Message struct {
Type string
MsgID int
Body interface{}
Peer *net.UnixAddr
Ucred *syscall.Ucred
Fds []int
mconn *MsgConn
}
type BaseMsg struct {
Type string
MsgID int
IsResponse bool
Body json.RawMessage
}
func (mc *MsgConn) parseMessage(data []byte) (*Message, error) {
var base BaseMsg
if err := json.Unmarshal(data, &base); err != nil {
return nil, err
}
body,err := mc.factory.create(base.Type)
if err != nil {
return nil, err
}
if err := json.Unmarshal(base.Body, body); err != nil {
return nil, err
}
m := new(Message)
m.Type = base.Type
m.MsgID = base.MsgID
m.Body = body
return m,nil
}
func (m *Message) Free() {
for _,fd := range m.Fds {
syscall.Close(fd)
}
m.Fds = nil
}
func (m *Message) parseControlData(data []byte) error {
cmsgs, err := syscall.ParseSocketControlMessage(data)
if err != nil {
return err
}
for _, cmsg := range cmsgs {
switch cmsg.Header.Type {
case syscall.SCM_CREDENTIALS:
cred, err := syscall.ParseUnixCredentials(&cmsg)
if err != nil {
return err
}
m.Ucred = cred
case syscall.SCM_RIGHTS:
fds, err := syscall.ParseUnixRights(&cmsg)
if err != nil {
return err
}
m.Fds = fds
}
}
return nil
}
func (m *Message) Respond(msg interface{}, fds... int) error {
return m.mconn.sendMessage(msg, m.MsgID, m.Peer, fds...)
}

@ -0,0 +1,78 @@
package ipc
import (
"time"
"sync"
)
type ResponseReader interface {
Chan() <-chan *Message
Done()
}
type responseWaiter struct {
rm *responseManager
id int
timeout time.Time
ch chan *Message
}
func (rw *responseWaiter) Chan() <-chan *Message {
return rw.ch
}
func (rw *responseWaiter) Done() {
rw.rm.lock.Lock()
defer rw.rm.lock.Unlock()
close(rw.ch)
delete(rw.rm.responseMap, rw.id)
}
type responseManager struct {
lock sync.Locker
responseMap map[int]*responseWaiter
}
func newResponseManager() *responseManager {
rm := new(responseManager)
rm.lock = new(sync.Mutex)
rm.responseMap = make(map[int]*responseWaiter)
return rm
}
func (rm *responseManager) register(id int) ResponseReader {
ch := make(chan *Message)
rm.lock.Lock()
defer rm.lock.Unlock()
rm.removeById(id, true)
rw := &responseWaiter{
rm: rm,
id: id,
ch: ch,
}
rm.responseMap[id] = rw
return rw
}
func (rm *responseManager) handle(m *Message) bool {
rm.lock.Lock()
defer rm.lock.Unlock()
rw := rm.responseMap[m.MsgID]
if rw == nil {
return false
}
rw.ch <- m
return true
}
func (rm *responseManager) removeById(id int, klose bool) *responseWaiter{
rw := rm.responseMap[id]
if rw == nil {
return nil
}
delete(rm.responseMap, id)
if klose {
close(rw.ch)
}
return rw
}

@ -0,0 +1,58 @@
package ipc
import (
"testing"
)
func TestRegister(t *testing.T) {
rm := newResponseManager()
if len(rm.responseMap) != 0 {
t.Error("responseMap should be empty")
}
rm.register(1)
rm.register(2)
rm.register(1)
if len(rm.responseMap) != 2 {
t.Errorf("responseMap should have 2 items, not %d", len(rm.responseMap))
}
}
func TestRemoveById(t *testing.T) {
rm := newResponseManager()
rm.register(1)
rm.register(2)
rm.register(3)
rm.removeById(2, true)
rm.removeById(2, true)
if len(rm.responseMap) != 2 {
t.Errorf("responseMap should have 2 items, not %d", len(rm.responseMap))
}
rm.removeById(1, true)
rm.removeById(3, true)
if len(rm.responseMap) != 0 {
t.Errorf("responseMap should have 0 items, not %d", len(rm.responseMap))
}
}
func TestHandle(t *testing.T) {
m := new(Message)
rm := newResponseManager()
rr := rm.register(1)
rm.register(2)
m.MsgID = 3
if rm.handle(m) {
t.Errorf("handle() should have returned false")
}
go func() {
<-rr.Chan()
}()
m.MsgID = 1
if !rm.handle(m) {
t.Errorf("handle() should have returned true")
}
if len(rm.responseMap) != 2 {
t.Errorf("responseMap should have 2 items after handling message")
}
}

@ -0,0 +1,26 @@
package ipc
import (
"net"
"reflect"
"syscall"
)
func setPassCred(c net.Conn) error {
fd := reflectFD(c)
return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
}
func reflectFD(c net.Conn) int {
sysfd := extractField(c, "fd", "sysfd")
return int(sysfd.Int())
}
func extractField(ob interface{}, fieldNames ...string) reflect.Value {
v := reflect.Indirect(reflect.ValueOf(ob))
for _, fn := range fieldNames {
field := v.FieldByName(fn)
v = reflect.Indirect(field)
}
return v
}

@ -0,0 +1,141 @@
package daemon
import (
"github.com/subgraph/oz/ipc"
"errors"
"strconv"
"fmt"
)
func clientConnect() (*ipc.MsgConn, error) {
c := ipc.NewMsgConn(messageFactory, SocketName)
if err := c.Connect(); err != nil {
return nil, err
}
return c, nil
}
func clientSend(msg interface{}) (*ipc.Message, error) {
c,err := clientConnect()
if err != nil {
return nil, err
}
rr, err := c.ExchangeMsg(msg)
resp := <- rr.Chan()
rr.Done()
c.Close()
if err != nil {
return nil, err
}
return resp,nil
}
func ListProfiles() ([]Profile, error) {
resp,err := clientSend(new(ListProfilesMsg))
if err != nil {
return nil, err
}
body,ok := resp.Body.(*ListProfilesResp)
if !ok {
return nil, errors.New("ListProfiles response was not expected type")
}
return body.Profiles, nil
}
func ListSandboxes() ([]SandboxInfo, error) {
resp,err := clientSend(&ListSandboxesMsg{})
if err != nil {
return nil, err
}
body,ok := resp.Body.(*ListSandboxesResp)
if !ok {
return nil, errors.New("ListSandboxes response was not expected type")
}
return body.Sandboxes, nil
}
func Launch(arg string) error {
idx,name,err := parseProfileArg(arg)
if err != nil {
return err
}
resp,err := clientSend(&LaunchMsg{
Index: idx,
Name: name,
})
if err != nil {
return err
}
switch body := resp.Body.(type) {
case *ErrorMsg:
fmt.Printf("error was %s\n", body.Msg)
case *OkMsg:
fmt.Println("ok received")
default:
fmt.Printf("Unexpected message received %+v", body)
}
return nil
}
func Clean(arg string) error {
idx,name,err := parseProfileArg(arg)
if err != nil {
return err
}
resp,err := clientSend(&CleanMsg{
Index: idx,
Name: name,
})
if err != nil {
return err
}
// TODO collapse this logic into a function like clientSend
switch body := resp.Body.(type) {
case *ErrorMsg:
return errors.New(body.Msg)
case *OkMsg:
return nil
default:
return fmt.Errorf("Unexpected message received %+v", body)
}
}
func parseProfileArg(arg string) (int, string, error) {
if len(arg) == 0 {
return 0, "", errors.New("profile argument needed")
}
if n,err := strconv.Atoi(arg); err == nil {
return n, "", nil
}
return 0, arg, nil
}
func Logs(count int, follow bool) (chan string, error) {
c,err := clientConnect()
if err != nil {
return nil, err
}
rr,err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
if err != nil {
return nil, err
}
out := make(chan string)
go dumpLogs(out, rr)
return out, nil
}
func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
for resp := range rr.Chan() {
switch body := resp.Body.(type) {
case *OkMsg:
rr.Done()
close(out)
return
case *LogData:
for _, ll := range body.Lines {
out <- ll
}
default:
out <- fmt.Sprintf("Unexpected response type (%T)", body)
}
}
}

@ -0,0 +1,6 @@
package daemon
type Config struct {
profileDir string `json:"profile_dir"`
}

@ -0,0 +1,158 @@
package daemon
import (
"fmt"
"github.com/op/go-logging"
"github.com/subgraph/oz"
"github.com/subgraph/oz/ipc"
"syscall"
"github.com/subgraph/oz/fs"
)
type daemonState struct {
log *logging.Logger
profiles oz.Profiles
sandboxes []*Sandbox
nextSboxId int
memBackend *logging.ChannelMemoryBackend
backends []logging.Backend
}
func Main() {
d := initialize()
err := runServer(
d.handlePing,
d.handleListProfiles,
d.handleLaunch,
d.handleListSandboxes,
d.handleClean,
d.handleLogs,
)
if err != nil {
d.log.Warning("Error running server: %v", err)
}
}
func initialize() *daemonState {
d := &daemonState{}
d.initializeLogging()
ps,err := oz.LoadProfiles("/var/lib/oz/cells.d")
if err != nil {
d.log.Fatalf("Failed to load profiles: %v", err)
}
d.Debug("%d profiles loaded", len(ps))
d.profiles = ps
oz.ReapChildProcs(d.log, d.handleChildExit)
d.nextSboxId = 1
return d
}
func (d *daemonState) handleChildExit(pid int, wstatus syscall.WaitStatus) {
d.Debug("Child process pid=%d exited with status %d", pid, wstatus.ExitStatus())
for _,sbox := range d.sandboxes {
if sbox.init.Process.Pid == pid {
sbox.fs.Cleanup()
}
}
}
func runServer(args ...interface{}) error {
serv := ipc.NewMsgConn(messageFactory, SocketName)
if err := serv.AddHandlers(args...); err != nil {
return err
}
if err := serv.Listen(); err != nil {
return err
}
return serv.Run()
}
func (d * daemonState) handlePing(msg *PingMsg, m *ipc.Message) error {
d.Debug("received ping with data [%s]", msg.Data)
return m.Respond(&PingMsg{msg.Data})
}
func (d * daemonState) handleListProfiles(msg *ListProfilesMsg, m *ipc.Message) error {
r := new(ListProfilesResp)
index := 1
for _,p := range d.profiles {
r.Profiles = append(r.Profiles, Profile{Index: index, Name: p.Name, Path: p.Path})
index += 1
}
return m.Respond(r)
}
func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error {
d.Debug("Launch message received: %+v", msg)
p,err := d.getProfileByIdxOrName(msg.Index, msg.Name)
if err != nil {
return m.Respond(&ErrorMsg{err.Error()})
}
d.Debug("Would launch %s", p.Name)
_,err = d.launch(p)
if err != nil {
d.Warning("launch of %s failed: %v", p.Name, err)
return m.Respond(&ErrorMsg{err.Error()})
}
return m.Respond(&OkMsg{})
}
func (d *daemonState) getProfileByIdxOrName(index int, name string) (*oz.Profile, error) {
if len(name) == 0 {
if index < 1 || index > len(d.profiles) {
return nil, fmt.Errorf("not a valid profile index (%d)", index)
}
return d.profiles[index-1], nil
}
for _,p := range d.profiles {
if p.Name == name {
return p,nil
}
}
return nil, fmt.Errorf("could not find profile name '%s'", name)
}
func (d *daemonState) handleListSandboxes(list *ListSandboxesMsg, msg *ipc.Message) error {
r := new(ListSandboxesResp)
for _, sb := range d.sandboxes {
r.Sandboxes = append(r.Sandboxes, SandboxInfo{Id: sb.id, Address: sb.addr, Profile: sb.profile.Name})
}
return msg.Respond(r)
}
func (d *daemonState) handleClean(clean *CleanMsg, msg *ipc.Message) error {
p,err := d.getProfileByIdxOrName(clean.Index, clean.Name)
if err != nil {
return msg.Respond(&ErrorMsg{err.Error()})
}
for _, sb := range d.sandboxes {
if sb.profile.Name == p.Name {
errmsg := fmt.Sprintf("Cannot clean profile '%s' because there are sandboxes running for this profile", p.Name)
return msg.Respond(&ErrorMsg{errmsg})
}
}
fs := fs.NewFromProfile(p, d.log)
if err := fs.Cleanup(); err != nil {
return msg.Respond(&ErrorMsg{err.Error()})
}
return msg.Respond(&OkMsg{})
}
func (d *daemonState) handleLogs(logs *LogsMsg, msg *ipc.Message) error {
for n := d.memBackend.Head(); n != nil; n = n.Next() {
s := n.Record.Formatted(0)
msg.Respond(&LogData{Lines: []string{s}})
}
if logs.Follow {
d.followLogs(msg)
return nil
}
msg.Respond(&OkMsg{})
return nil
}

@ -0,0 +1,128 @@
package daemon
import (
"github.com/subgraph/oz"
"github.com/subgraph/oz/fs"
"os/exec"
"github.com/subgraph/oz/ipc"
"syscall"
"fmt"
"io"
"bufio"
)
const initPath = "/usr/local/bin/oz-init"
type Sandbox struct {
daemon *daemonState
id int
profile *oz.Profile
init *exec.Cmd
fs *fs.Filesystem
stderr io.ReadCloser
addr string
}
/*
func findSandbox(id int) *Sandbox {
for _, sb := range sandboxes {
if sb.id == id {
return sb
}
}
return nil
}
*/
const initCloneFlags = syscall.CLONE_NEWNS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUTS
func createInitCommand(addr, name, chroot string) *exec.Cmd {
cmd := exec.Command(initPath)
cmd.Dir = "/"
cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: chroot,
Cloneflags: initCloneFlags,
}
cmd.Env = []string{
"INIT_ADDRESS="+addr,
"INIT_PROFILE="+name,
}
return cmd
}
func (d *daemonState) launch(p *oz.Profile) (*Sandbox, error) {
fs := fs.NewFromProfile(p, d.log)
if err := fs.Setup(); err != nil {
return nil, err
}
addr,err := ipc.CreateRandomAddress("@oz-init-")
if err != nil {
return nil, err
}
cmd := createInitCommand(addr, p.Name, fs.Root())
pp,err := cmd.StderrPipe()
if err != nil {
fs.Cleanup()
return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)
}
if err := cmd.Start(); err != nil {
fs.Cleanup()
return nil, err
}
sbox := &Sandbox{
daemon: d,
id: d.nextSboxId,
profile: p,
init: cmd,
fs: fs,
addr: addr,
stderr: pp,
}
go sbox.logMessages()
d.nextSboxId += 1
d.sandboxes = append(d.sandboxes, sbox)
return sbox,nil
}
func (sbox *Sandbox) logMessages() {
scanner := bufio.NewScanner(sbox.stderr)
for scanner.Scan() {
line := scanner.Text()
if len(line) > 1 {
sbox.logLine(line)
}
}
sbox.stderr.Close()
}
func (sbox *Sandbox) logLine(line string) {
if len(line) < 2 {
return
}
f := sbox.getLogFunc(line[0])
msg := line[2:]
if f != nil {
f("[%s] %s", sbox.profile.Name, msg)
} else {
sbox.daemon.log.Info("[%s] %s", sbox.profile.Name, line)
}
}
func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
log := sbox.daemon.log
switch(c) {
case 'D':
return log.Debug
case 'I':
return log.Info
case 'N':
return log.Notice
case 'W':
return log.Warning
case 'E':
return log.Error
case 'C':
return log.Critical
}
return nil
}

@ -0,0 +1,90 @@
package daemon
import (
"github.com/op/go-logging"
"log"
"os"
"github.com/subgraph/oz/ipc"
)
func (d *daemonState) Debug(format string, args ...interface{}) {
d.log.Debug(format, args...)
}
func (d *daemonState) Info(format string, args ...interface{}) {
d.log.Info(format, args...)
}
func (d *daemonState) Notice(format string, args ...interface{}) {
d.log.Notice(format, args...)
}
func (d *daemonState) Warning(format string, args ...interface{}) {
d.log.Warning(format, args...)
}
func (d *daemonState) Error(format string, args ...interface{}) {
d.log.Error(format, args...)
}
func (d *daemonState) Critical(format string, args ...interface{}) {
d.log.Critical(format, args...)
}
func (d *daemonState) initializeLogging() {
d.log = logging.MustGetLogger("oz")
be := logging.NewChannelMemoryBackend(100)
fbe := logging.NewBackendFormatter(be, format)
d.memBackend = be
stderr := logging.NewLogBackend(os.Stderr, "", log.LstdFlags)
d.backends = []logging.Backend{
stderr,
fbe,
}
d.installBackends()
}
var format = logging.MustStringFormatter(
"%{color}%{time:15:04:05} â–¶ %{level:.4s} %{id:03x}%{color:reset} %{message}",
)
func (d *daemonState) addBackend(be logging.Backend) {
d.backends = append(d.backends, be)
d.installBackends()
}
func (d *daemonState) removeBackend(be logging.Backend) {
newBackends := []logging.Backend{}
for _,b := range d.backends {
if b != be {
newBackends = append(newBackends, b)
}
}
d.backends = newBackends
d.installBackends()
}
func (d *daemonState) installBackends() {
if len(d.backends) == 1 {
d.log.SetBackend(logging.AddModuleLevel(d.backends[0]))
return
}
d.log.SetBackend(logging.MultiLogger(d.backends...))
}
type logFollower struct {
daemon *daemonState
wrapper logging.Backend
m *ipc.Message
}
func (lf *logFollower) Log(level logging.Level, calldepth int, rec *logging.Record) error {
s := rec.Formatted(calldepth)
if err := lf.m.Respond(&LogData{[]string{s}}); err != nil {
lf.remove()
}
return nil
}
func (lf *logFollower) remove() {
lf.daemon.removeBackend(lf.wrapper)
}
func (d *daemonState) followLogs(m *ipc.Message) {
be := &logFollower{m:m, daemon: d}
be.wrapper = logging.NewBackendFormatter(be, format)
d.addBackend(be.wrapper)
}

@ -0,0 +1,78 @@
package daemon
import "github.com/subgraph/oz/ipc"
const SocketName = "@oz-control"
type OkMsg struct {
_ string "Ok"
}
type ErrorMsg struct {
Msg string "Error"
}
type PingMsg struct {
Data string "Ping"
}
type ListProfilesMsg struct {
_ string "ListProfiles"
}
type Profile struct {
Index int
Name string
Path string
}
type ListProfilesResp struct {
Profiles []Profile "ListProfilesResp"
}
type LaunchMsg struct {
Index int "Launch"
Name string
}
type ListSandboxesMsg struct {
_ string "ListSandboxes"
}
type SandboxInfo struct {
Id int
Address string
Profile string
}
type ListSandboxesResp struct {
Sandboxes []SandboxInfo "ListSandboxesResp"
}
type CleanMsg struct {
Index int "Clean"
Name string
}
type LogsMsg struct {
Count int "Logs"
Follow bool
}
type LogData struct {
Lines []string "LogData"
}
var messageFactory = ipc.NewMsgFactory(
new(PingMsg),
new(OkMsg),
new(ErrorMsg),
new(ListProfilesMsg),
new(ListProfilesResp),
new(LaunchMsg),
new(ListSandboxesMsg),
new(ListSandboxesResp),
new(CleanMsg),
new(LogsMsg),
new(LogData),
)

@ -0,0 +1 @@
package daemon

@ -0,0 +1,72 @@
package ozinit
import (
"github.com/subgraph/oz/ipc"
"errors"
"fmt"
)
func clientConnect(addr string) (*ipc.MsgConn, error) {
c := ipc.NewMsgConn(messageFactory, addr)
if err := c.Connect(); err != nil {
return nil, err
}
return c, nil
}
func clientSend(addr string, msg interface{}) (*ipc.Message, error) {
c,err := clientConnect(addr)
if err != nil {
return nil, err
}
rr, err := c.ExchangeMsg(msg)
resp := <- rr.Chan()
rr.Done()
c.Close()
if err != nil {
return nil, err
}
return resp, nil
}
func Ping(addr string) error {
resp,err := clientSend(addr, new(PingMsg))
if err != nil {
return err
}
switch body := resp.Body.(type) {
case *PingMsg:
return nil
case *ErrorMsg:
return errors.New(body.Msg)
default:
return fmt.Errorf("Unexpected message received: %+v", body)
}
}
func RunShell(addr, term string) (int, error) {
c,err := clientConnect(addr)
if err != nil {
return 0, err
}
rr,err := c.ExchangeMsg(&RunShellMsg{Term: term})
resp := <- rr.Chan()
rr.Done()
c.Close()
if err != nil {
return 0, err
}
switch body := resp.Body.(type) {
case *ErrorMsg:
return 0,errors.New(body.Msg)
case *OkMsg:
if len(resp.Fds) == 0 {
return 0, errors.New("RunShell message returned Ok, but no file descriptor received")
}
return resp.Fds[0], nil
default:
return 0, fmt.Errorf("Unexpected message type received: %+v", body)
}
}

@ -0,0 +1,160 @@
package ozinit
import (
"os"
"github.com/codegangsta/cli"
"github.com/subgraph/oz"
"github.com/subgraph/oz/fs"
"github.com/subgraph/oz/ipc"
"os/exec"
"syscall"
"github.com/op/go-logging"
"github.com/kr/pty"
"fmt"
)
const profileDirectory = "/var/lib/oz/cells.d"
var log = createLogger()
func createLogger() *logging.Logger {
l := logging.MustGetLogger("oz-init")
be := logging.NewLogBackend(os.Stderr, "", 0)
f := logging.MustStringFormatter("%{level:.1s} %{message}")
fbe := logging.NewBackendFormatter(be,f)
logging.SetBackend(fbe)
return l
}
var allowRootShell = false
var profileName = "none"
func Main() {
app := cli.NewApp()
app.Name = "oz-init"
app.Action = runInit
app.Flags = []cli.Flag {
cli.StringFlag {
Name: "address",
Usage: "unix socket address to listen for commands on",
EnvVar: "INIT_ADDRESS",
},
cli.StringFlag{
Name: "profile",
Usage: "name of profile to launch",
EnvVar: "INIT_PROFILE",
},
}
app.Run(os.Args)
}
func runInit(c *cli.Context) {
address := c.String("address")
profile := c.String("profile")
if address == "" {
log.Error("Error: missing required 'address' argument")
os.Exit(1)
}
if profile == "" {
log.Error("Error: missing required 'profile' argument")
os.Exit(1)
}
profileName = profile
log.Info("Starting oz-init for profile: %s", profile)
log.Info("Socket address: %s", address)
p,err := loadProfile(profile)
if err != nil {
log.Error("Could not load profile %s: %v", profile, err)
os.Exit(1)
}
fs := fs.NewFromProfile(p, log)
if err := fs.OzInit(); err != nil {
log.Error("Error: setting up filesystem failed: %v\n", err)
os.Exit(1)
}
oz.ReapChildProcs(log, handleChildExit)
serv := ipc.NewMsgConn(messageFactory, address)
serv.AddHandlers(
handlePing,
handleRunShell,
)
serv.Listen()
serv.Run()
log.Info("oz-init exiting...")
}
func loadProfile(name string) (*oz.Profile, error) {
ps,err := oz.LoadProfiles(profileDirectory)
if err != nil {
return nil, err
}
for _,p := range ps {
if name == p.Name {
return p, nil
}
}
return nil, fmt.Errorf("no profile named '%s'", name)
}
func handlePing(ping *PingMsg, msg *ipc.Message) error {
return msg.Respond(&PingMsg{Data: ping.Data})
}
func handleRunShell(rs *RunShellMsg, msg *ipc.Message) error {
if msg.Ucred == nil {
return msg.Respond(&ErrorMsg{"No credentials received for RunShell command"})
}
if msg.Ucred.Uid == 0 || msg.Ucred.Gid == 0 && !allowRootShell {
return msg.Respond(&ErrorMsg{"Cannot open shell because allowRootShell is disabled"})
}
log.Info("Starting shell with uid = %d, gid = %d", msg.Ucred.Uid, msg.Ucred.Gid)
cmd := exec.Command("/bin/sh", "-i")
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: msg.Ucred.Uid,
Gid: msg.Ucred.Gid,
}
if rs.Term != "" {
cmd.Env = append(cmd.Env, "TERM="+rs.Term)
}
cmd.Env = append(cmd.Env, "PATH=/usr/bin:/bin")
cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=[%s] $ ", profileName))
log.Info("Executing shell...")
f,err := ptyStart(cmd)
defer f.Close()
if err != nil {
return msg.Respond(&ErrorMsg{err.Error()})
}
err = msg.Respond(&OkMsg{}, int(f.Fd()))
return err
}
func ptyStart(c *exec.Cmd) (ptty *os.File, err error) {
ptty,tty, err := pty.Open()
if err != nil {
return nil, err
}
defer tty.Close()
c.Stdin = tty
c.Stdout = tty
c.Stderr = tty
if c.SysProcAttr == nil {
c.SysProcAttr = &syscall.SysProcAttr{}
}
c.SysProcAttr.Setctty = true
c.SysProcAttr.Setsid = true
if err := c.Start(); err != nil {
ptty.Close()
return nil, err
}
return ptty, nil
}
func handleChildExit(pid int, wstatus syscall.WaitStatus) {
log.Debug("Child process pid=%d exited with status %d", pid, wstatus.ExitStatus())
}

@ -0,0 +1,26 @@
package ozinit
import "github.com/subgraph/oz/ipc"
type OkMsg struct {
_ string "Ok"
}
type ErrorMsg struct {
Msg string "Error"
}
type PingMsg struct {
Data string "Ping"
}
type RunShellMsg struct {
Term string "RunShell"
}
var messageFactory = ipc.NewMsgFactory(
new(OkMsg),
new(ErrorMsg),
new(PingMsg),
new(RunShellMsg),
)

@ -0,0 +1,168 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"os"
"github.com/subgraph/oz/oz-daemon"
"strconv"
"io"
"github.com/subgraph/oz/oz-init"
)
func main() {
app := cli.NewApp()
app.Name = "oz"
app.Usage = "command line interface to Oz sandboxes"
app.Author = "Subgraph"
app.Email = "info@subgraph.com"
app.Commands = []cli.Command {
{
Name: "profiles",
Usage: "list available application profiles",
Action: handleProfiles,
},
{
Name: "launch",
Usage: "launch an application profile",
Action: handleLaunch,
},
{
Name: "list",
Usage: "list running sandboxes",
Action: handleList,
},
{
Name: "shell",
Usage: "start a shell in a running container",
Action: handleShell,
},
{
Name: "clean",
Action: handleClean,
},
{
Name: "logs",
Action: handleLogs,
Flags: []cli.Flag {
cli.BoolFlag{
Name: "f",
},
},
},
}
app.Run(os.Args)
}
func handleProfiles(c *cli.Context) {
ps,err := daemon.ListProfiles()
if err != nil {
fmt.Printf("Error listing profiles: %v\n", err)
os.Exit(1)
}
for i,p := range ps {
fmt.Printf("%2d) %-30s %s\n", i+1, p.Name, p.Path)
}
}
func handleLaunch(c *cli.Context) {
if len(c.Args()) == 0 {
fmt.Println("Argument needed to launch command")
os.Exit(1)
}
err := daemon.Launch(c.Args()[0])
if err != nil {
fmt.Printf("launch command failed: %v\n", err)
}
}
func handleList(c *cli.Context) {
sboxes,err := daemon.ListSandboxes()
if err != nil {
fmt.Printf("Error listing running containers: %v\n", err)
os.Exit(1)
}
if len(sboxes) == 0 {
fmt.Println("No running containers")
return
}
for _, sb := range sboxes {
fmt.Printf("%2d) %s\n", sb.Id, sb.Profile)
}
}
func handleShell(c *cli.Context) {
if len(c.Args()) == 0 {
fmt.Println("Sandbox id argument needed")
os.Exit(1)
}
id,err := strconv.Atoi(c.Args()[0])
if err != nil {
fmt.Println("Sandbox id argument must be an integer")
os.Exit(1)
}
sb,err := getSandboxById(id)
if err != nil {
fmt.Printf("Error retrieving sandbox list: %v\n", err)
}
if sb == nil {
fmt.Printf("No sandbox found with id = %d\n", id)
}
term := os.Getenv("TERM")
fd,err := ozinit.RunShell(sb.Address, term)
if err != nil {
fmt.Printf("start shell command failed: %v\n", err)
os.Exit(1)
}
fmt.Println("Entering interactive shell?\n")
st,err := SetRawTerminal(0)
HandleResize(fd)
f := os.NewFile(uintptr(fd), "")
go io.Copy(f, os.Stdin)
io.Copy(os.Stdout, f)
RestoreTerminal(0, st)
fmt.Println("done..")
}
func getSandboxById(id int) (*daemon.SandboxInfo, error) {
sboxes,err := daemon.ListSandboxes()
if err != nil {
return nil, err
}
for _, sb := range sboxes {
if id == sb.Id {
return &sb,nil
}
}
return nil, nil
}
func handleClean(c *cli.Context) {
if len(c.Args()) == 0 {
fmt.Println("Need a profile to clean")
os.Exit(1)
}
err := daemon.Clean(c.Args()[0])
if err != nil {
fmt.Println("Clean failed:", err)
}
}
func handleLogs(c *cli.Context) {
follow := c.Bool("f")
ch,err := daemon.Logs(0,follow)
if err != nil {
fmt.Println("Logs failed", err)
os.Exit(1)
}
for ll := range ch {
fmt.Println(ll)
}
}

@ -0,0 +1,112 @@
package main
import (
"syscall"
"unsafe"
"os"
"os/signal"
"fmt"
)
type winsize struct {
Height uint16
Width uint16
x uint16
y uint16
}
type State struct {
termios Termios
}
const (
getTermios = syscall.TCGETS
setTermios = syscall.TCSETS
)
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]byte
Ispeed uint32
Ospeed uint32
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
newState.Oflag &^= syscall.OPOST
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
newState.Cflag |= syscall.CS8
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
return nil, err
}
return &oldState, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func RestoreTerminal(fd uintptr, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
return err
}
func SetRawTerminal(fd uintptr) (*State, error) {
oldState, err := MakeRaw(fd)
if err != nil {
return nil, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
RestoreTerminal(fd, oldState)
os.Exit(0)
}()
return oldState, err
}
func GetWinsize(fd uintptr) (*winsize, syscall.Errno) {
ws := &winsize{}
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
return ws, err
}
func SetWinsize(fd uintptr, ws *winsize) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
return err
}
func HandleResize(fd int) {
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGWINCH)
go func() {
for {
<-sigs
handleSIGWINCH(fd)
}
}()
handleSIGWINCH(fd)
}
func handleSIGWINCH(fd int) {
ws,errno := GetWinsize(0)
if errno != 0 {
fmt.Printf("error reading winsize: %v\n", errno.Error())
return
}
if errno := SetWinsize(uintptr(fd), ws); errno != 0 {
fmt.Printf("error setting winsize: %v", errno.Error())
}
}

@ -0,0 +1,118 @@
package oz
import (
"encoding/json"
"fmt"
"io/ioutil"
"path"
)
type Profile struct {
// Name of this profile
Name string
// Path to binary to launch
Path string
// Optional path of binary to watch for watchdog purposes if different than Path
Watchdog string
// Optional wrapper binary to use when launching command (ex: tsocks)
Wrapper string
// If true launch one container per instance, otherwise run all instances in same container
Multi bool
// Disable mounting of sys and proc inside the container
NoSysProc bool
// Disable bind mounting of default directories (etc,usr,bin,lib,lib64)
// Also disables default blacklist items (/sbin, /usr/sbin, /usr/bin/sudo)
// Normally not used
NoDefaults bool
// List of paths to bind mount inside jail
Whitelist []WhitelistItem
// List of paths to blacklist inside jail
Blacklist []BlacklistItem
// Optional XServer config
XServer XServerConf
// List of environment variables
Environment []EnvVar
}
type XServerConf struct {
Enabled bool
TrayIcon string `json:"tray_icon"`
WindowIcon string `json:"window_icon"`
EnableTray bool `json:"enable_tray"`
UseDBUS bool `json:"use_dbus"`
UsePulseAudio bool `json:"use_pulse_audio"`
DisableClipboard bool `json:"disable_clipboard"`
DisableAudio bool `json:"disable_audio"`
WorkDir string `json:"work_dir"`
}
type WhitelistItem struct {
Path string
ReadOnly bool
}
type BlacklistItem struct {
Path string
}
type EnvVar struct {
Name string
Value string
}
const defaultProfileDirectory = "/var/lib/oz/cells.d"
var loadedProfiles []*Profile
type Profiles []*Profile
func (ps Profiles) GetProfileByName(name string) (*Profile,error) {
if loadedProfiles == nil {
ps ,err := LoadProfiles(defaultProfileDirectory)
if err != nil {
return nil, err
}
loadedProfiles = ps
}
for _,p := range loadedProfiles {
if p.Name == name {
return p,nil
}
}
return nil, nil
}
func LoadProfiles(dir string) (Profiles, error) {
fs, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
ps := []*Profile{}
for _, f := range fs {
if !f.IsDir() {
name := path.Join(dir, f.Name())
p, err := loadProfileFile(name)
if err != nil {
return nil, fmt.Errorf("error loading '%s': %v", f.Name(), err)
}
ps = append(ps, p)
}
}
return ps, nil
}
func loadProfileFile(file string) (*Profile, error) {
bs, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
p := new(Profile)
if err := json.Unmarshal(bs, p); err != nil {
return nil, err
}
if p.Name == "" {
p.Name = path.Base(p.Path)
}
return p, nil
}

@ -0,0 +1,17 @@
{
"path": "/usr/bin/evince"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/hicolor/48x48/apps/evince.png"
}
, "network":{
"nettype":"empty"
}
, "whitelist": [
]
, "blacklist": [
]
, "environment": [
]
}

@ -0,0 +1,33 @@
{
"path": "/usr/bin/gajim"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/gajim.svg"
, "disable_audio":true
}
, "network":{
"nettype":"empty"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5222}
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5269}
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5000}
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5280}
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5281}
]
}
, "whitelist": [
{"source":"/run/resolvconf"}
, {"source":"/run/user/${UID}/keyring-*"}
, {"source":"${HOME}/.local/share/gajim"}
, {"source":"${HOME}/.cache/gajim"}
, {"source":"${HOME}/.config/gajim"}
, {"source":"${HOME}/.local/share/keyrings"}
]
, "blacklist": [
{"source":"/run/user/${UID}/keyring-*/ssh"}
, {"source":"/run/user/${UID}/keyring-*/pkcs11"}
, {"source":"/run/user/${UID}/keyring-*/gpg"}
]
}

@ -0,0 +1,34 @@
{
"path": "/usr/bin/icedove"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon": "/usr/share/icons/hicolor/scalable/apps/icedove.svg"
, "disable_audio": true
}
, "network":{
"nettype":"bridge"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
]
}
, "whitelist": [
{"source":"/run/resolvconf"}
, {"source":"/run/user/${UID}/keyring"}
, {"source":"/tmp/gpg-*"}
, {"source":"${HOME}/.cache/icedove"}
, {"source":"${HOME}/.gnupg"}
, {"source":"${HOME}/..thunderbird"}
, {"source":"${HOME}/.icedove"}
, {"source":"${HOME}/.config/gtk-3.0"}
, {"source":"${HOME}/.config/gtk-2.0"}
]
, "_blacklist": [
]
, "environment": [
{"name":"GPG_AGENT_INFO"}
, {"name":"GNOME_KEYRING_CONTROL"}
, {"name":"GNOME_KEYRING_PID", "value":"1"}
]
}

@ -0,0 +1,27 @@
{
"path": "/usr/bin/iceweasel"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/hicolor/scalable/apps/iceweasel.svg"
}
, "network":{
"nettype":"bridge"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
]
}
, "whitelist": [
{"source":"/run/resolvconf"}
, {"source":"${HOME}/.mozilla"}
, {"source":"${HOME}/.cache/mozilla"}
, {"source":"${HOME}/Downloads/"}
, {"source":"${HOME}/.config/gtk-3.0"}
, {"source":"${HOME}/.config/gtk-2.0"}
]
, "blacklist": [
]
, "environment": [
]
}

@ -0,0 +1,34 @@
{
"path": "/usr/bin/liferea"
, "_wrapper": "/usr/bin/torify"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/hicolor/scalable/apps/liferea.svg"
, "_tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/liferea.svg"
}
, "network":{
"nettype":"bridge"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
]
}
, "whitelist": [
{"source":"/run/resolvconf"}
, {"source":"${HOME}/.local/share/liferea"}
, {"source":"${HOME}/.cache/liferea"}
, {"source":"${HOME}/.config/liferea"}
, {"source":"${HOME}/.config/gtk-3.0"}
, {"source":"${HOME}/.config/gtk-2.0"}
, {"source":"${HOME}/.config/dconf"}
, {"source":"${HOME}/.cache/dconf"}
, {"source":"/run/user/${UID}/dconf"}
]
, "blacklist": [
]
, "_environment": [
]
}

@ -0,0 +1,18 @@
{
"path": "/usr/bin/pidgin"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/pidgin-menu.svg"
}
, "network":{
"nettype":"bridge"
}
, "whitelist": [
{"source":"${HOME}/.purple"}
]
, "blacklist": [
]
, "environment": [
]
}

@ -0,0 +1,29 @@
{
"path": "/usr/local/bin/pond"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/office-mail.svg"
, "window_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/office-mail.svg"
, "disable_audio":true
}
, "network":{
"nettype":"empty"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
, {"nettype":"client", "proto":"tcp", "port":30003}
]
}
, "whitelist": [
{"source":"${HOME}/.pond"}
, {"source":"/opt/usr/share/gopkgs/pond"}
]
, "blacklist": [
]
, "environment": [
{"name":"GOPATH", "value":"/opt/usr/share/gopkgs/pond"}
, {"name":"TOR_SKIP_LAUNCH"}
, {"name":"TOR_SOCKS_HOST"}
, {"name":"TOR_SOCKS_PORT"}
]
}

@ -0,0 +1,30 @@
{
"path": "/usr/bin/torbrowser-launcher"
, "watchdog": "start-tor-browser"
, "xserver": {
"enabled": true
, "enable_tray": true
, "disable_audio": true
, "disable_clipboard": false
, "tray_icon":"/usr/share/pixmaps/torbrowser80.xpm"
}
, "network":{
"nettype":"empty"
, "sockets": [
{"nettype":"client", "proto":"tcp", "port":9050}
]
}
, "whitelist": [
{"source":"${HOME}/.local/share/torbrowser"}
, {"source":"${HOME}/.cache/torbrowser"}
, {"source":"${HOME}/.config/torbrowser"}
, {"source":"${HOME}/Downloads/TorBrowser"}
]
, "blacklist": [
]
, "environment": [
{"name":"TOR_SKIP_LAUNCH"}
, {"name":"TOR_SOCKS_HOST"}
, {"name":"TOR_SOCKS_PORT"}
]
}

@ -0,0 +1,24 @@
{
"path": "/usr/bin/xchat"
, "xserver": {
"enabled": true
, "enable_tray": true
, "tray_icon": "/usr/share/icons/gnome-colors-common/scalable/apps/xchat.svg"
, "disable_audio": true
}
, "network":{
"nettype":"empty"
, "sockets": [
{"nettype":"client", "destination": "10.10.0.90", "proto":"tcp", "port":57000}
]
}
, "whitelist": [
{"source":"${HOME}/.xpra/xchat"}
, {"source":"${HOME}/.xchat2"}
, {"source":"${HOME}/.config/gtk-3.0"}
, {"source":"${HOME}/.config/gtk-2.0"}
]
, "blacklist": [
]
}
Loading…
Cancel
Save