Compare commits
No commits in common. "81163cc60f2cb0648027b8ed351de9f998de306c" and "e3f1d7ba60bfc727e8308c386a306928fcf10130" have entirely different histories.
81163cc60f
...
e3f1d7ba60
@ -1,26 +0,0 @@
|
|||||||
name: Nix
|
|
||||||
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
name: NixOS tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30
|
|
||||||
with:
|
|
||||||
# explicitly enable sandbox
|
|
||||||
install_options: --daemon
|
|
||||||
extra_nix_config: |
|
|
||||||
sandbox = true
|
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
|
||||||
enable_kvm: true
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: nix --print-build-logs --experimental-features 'nix-command flakes' flake check --all-systems
|
|
@ -1,4 +1,4 @@
|
|||||||
name: Release
|
name: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -7,7 +7,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: node:16-bookworm-slim
|
image: node:16-bookworm-slim
|
||||||
@ -17,7 +16,6 @@ jobs:
|
|||||||
echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list.d/backports.list &&
|
echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list.d/backports.list &&
|
||||||
apt-get update &&
|
apt-get update &&
|
||||||
apt-get install -y
|
apt-get install -y
|
||||||
acl
|
|
||||||
git
|
git
|
||||||
gcc
|
gcc
|
||||||
pkg-config
|
pkg-config
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
name: Tests
|
name: test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
- push
|
- push
|
||||||
@ -6,7 +6,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Go tests
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: node:16-bookworm-slim
|
image: node:16-bookworm-slim
|
||||||
@ -16,7 +15,6 @@ jobs:
|
|||||||
echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list.d/backports.list &&
|
echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list.d/backports.list &&
|
||||||
apt-get update &&
|
apt-get update &&
|
||||||
apt-get install -y
|
apt-get install -y
|
||||||
acl
|
|
||||||
git
|
git
|
||||||
gcc
|
gcc
|
||||||
pkg-config
|
pkg-config
|
||||||
|
19
acl/acl.go
19
acl/acl.go
@ -1,19 +0,0 @@
|
|||||||
// Package acl implements simple ACL manipulation via libacl.
|
|
||||||
package acl
|
|
||||||
|
|
||||||
type Perms []Perm
|
|
||||||
|
|
||||||
func (ps Perms) String() string {
|
|
||||||
var s = []byte("---")
|
|
||||||
for _, p := range ps {
|
|
||||||
switch p {
|
|
||||||
case Read:
|
|
||||||
s[0] = 'r'
|
|
||||||
case Write:
|
|
||||||
s[1] = 'w'
|
|
||||||
case Execute:
|
|
||||||
s[2] = 'x'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(s)
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package acl_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
getFAclInvocation struct {
|
|
||||||
cmd *exec.Cmd
|
|
||||||
val []*getFAclResp
|
|
||||||
pe []error
|
|
||||||
}
|
|
||||||
|
|
||||||
getFAclResp struct {
|
|
||||||
typ fAclType
|
|
||||||
cred int32
|
|
||||||
val fAclPerm
|
|
||||||
|
|
||||||
raw []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
fAclPerm uintptr
|
|
||||||
fAclType uint8
|
|
||||||
)
|
|
||||||
|
|
||||||
const fAclBufSize = 16
|
|
||||||
|
|
||||||
const (
|
|
||||||
fAclPermRead fAclPerm = 1 << iota
|
|
||||||
fAclPermWrite
|
|
||||||
fAclPermExecute
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fAclTypeUser fAclType = iota
|
|
||||||
fAclTypeGroup
|
|
||||||
fAclTypeMask
|
|
||||||
fAclTypeOther
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *getFAclInvocation) run(name string) error {
|
|
||||||
if c.cmd != nil {
|
|
||||||
panic("attempted to run twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)
|
|
||||||
|
|
||||||
scanErr := make(chan error, 1)
|
|
||||||
if p, err := c.cmd.StdoutPipe(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
go c.parse(p, scanErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Join(<-scanErr, c.cmd.Wait())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) {
|
|
||||||
c.val = make([]*getFAclResp, 0, 4+fAclBufSize)
|
|
||||||
|
|
||||||
s := bufio.NewScanner(pipe)
|
|
||||||
for s.Scan() {
|
|
||||||
fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3)
|
|
||||||
if len(fields) != 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := getFAclResp{}
|
|
||||||
|
|
||||||
switch string(fields[0]) {
|
|
||||||
case "user":
|
|
||||||
resp.typ = fAclTypeUser
|
|
||||||
case "group":
|
|
||||||
resp.typ = fAclTypeGroup
|
|
||||||
case "mask":
|
|
||||||
resp.typ = fAclTypeMask
|
|
||||||
case "other":
|
|
||||||
resp.typ = fAclTypeOther
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("unknown type %s", string(fields[0])))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields[1]) == 0 {
|
|
||||||
resp.cred = -1
|
|
||||||
} else {
|
|
||||||
if cred, err := strconv.Atoi(string(fields[1])); err != nil {
|
|
||||||
c.pe = append(c.pe, err)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
resp.cred = int32(cred)
|
|
||||||
if resp.cred < 0 {
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("credential %d out of range", resp.cred))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields[2]) != 3 {
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm length %d", len(fields[2])))
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
switch fields[2][0] {
|
|
||||||
case 'r':
|
|
||||||
resp.val |= fAclPermRead
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch fields[2][1] {
|
|
||||||
case 'w':
|
|
||||||
resp.val |= fAclPermWrite
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch fields[2][2] {
|
|
||||||
case 'x':
|
|
||||||
resp.val |= fAclPermExecute
|
|
||||||
case '-':
|
|
||||||
default:
|
|
||||||
c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.raw = make([]byte, len(s.Bytes()))
|
|
||||||
copy(resp.raw, s.Bytes())
|
|
||||||
c.val = append(c.val, &resp)
|
|
||||||
}
|
|
||||||
scanErr <- s.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *getFAclResp) String() string {
|
|
||||||
if r.raw != nil && len(r.raw) > 0 {
|
|
||||||
return string(r.raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "(user-initialised resp value)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *getFAclResp) equals(typ fAclType, cred int32, val fAclPerm) bool {
|
|
||||||
return r.typ == typ && r.cred == cred && r.val == val
|
|
||||||
}
|
|
125
acl/acl_test.go
125
acl/acl_test.go
@ -1,125 +0,0 @@
|
|||||||
package acl_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/acl"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testFileName = "acl.test"
|
|
||||||
|
|
||||||
var (
|
|
||||||
uid = os.Geteuid()
|
|
||||||
cred = int32(os.Geteuid())
|
|
||||||
|
|
||||||
testFilePath = path.Join(os.TempDir(), testFileName)
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdatePerm(t *testing.T) {
|
|
||||||
if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
|
|
||||||
t.Log("acl test skipped")
|
|
||||||
t.SkipNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, err := os.Create(testFilePath); err != nil {
|
|
||||||
t.Fatalf("Create: error = %v", err)
|
|
||||||
} else {
|
|
||||||
if err = f.Close(); err != nil {
|
|
||||||
t.Fatalf("Close: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := os.Remove(testFilePath); err != nil {
|
|
||||||
t.Fatalf("Remove: error = %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cur := getfacl(t, testFilePath)
|
|
||||||
|
|
||||||
t.Run("default entry count", func(t *testing.T) {
|
|
||||||
if len(cur) != 3 {
|
|
||||||
t.Fatalf("unexpected test file acl length %d", len(cur))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default clear mask", func(t *testing.T) {
|
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
|
||||||
}
|
|
||||||
if cur = getfacl(t, testFilePath); len(cur) != 4 {
|
|
||||||
t.Fatalf("UpdatePerm: %v", cur)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default clear consistency", func(t *testing.T) {
|
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
|
||||||
}
|
|
||||||
if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) {
|
|
||||||
t.Fatalf("UpdatePerm: %v, want %v", val, cur)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
testUpdate(t, "r--", cur, fAclPermRead, acl.Read)
|
|
||||||
testUpdate(t, "-w-", cur, fAclPermWrite, acl.Write)
|
|
||||||
testUpdate(t, "--x", cur, fAclPermExecute, acl.Execute)
|
|
||||||
testUpdate(t, "-wx", cur, fAclPermWrite|fAclPermExecute, acl.Write, acl.Execute)
|
|
||||||
testUpdate(t, "r-x", cur, fAclPermRead|fAclPermExecute, acl.Read, acl.Execute)
|
|
||||||
testUpdate(t, "rw-", cur, fAclPermRead|fAclPermWrite, acl.Read, acl.Write)
|
|
||||||
testUpdate(t, "rwx", cur, fAclPermRead|fAclPermWrite|fAclPermExecute, acl.Read, acl.Write, acl.Execute)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdate(t *testing.T, name string, cur []*getFAclResp, val fAclPerm, perms ...acl.Perm) {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
|
||||||
}
|
|
||||||
if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) {
|
|
||||||
t.Fatalf("UpdatePerm: %v, want %v", v, cur)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := acl.UpdatePerm(testFilePath, uid, perms...); err != nil {
|
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
|
||||||
}
|
|
||||||
r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred)
|
|
||||||
if r == nil {
|
|
||||||
t.Fatalf("UpdatePerm did not add an ACL entry")
|
|
||||||
}
|
|
||||||
if !r.equals(fAclTypeUser, cred, val) {
|
|
||||||
t.Fatalf("UpdatePerm(%s) = %s", name, r)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getfacl(t *testing.T, name string) []*getFAclResp {
|
|
||||||
c := new(getFAclInvocation)
|
|
||||||
if err := c.run(name); err != nil {
|
|
||||||
t.Fatalf("getfacl: error = %v", err)
|
|
||||||
}
|
|
||||||
if len(c.pe) != 0 {
|
|
||||||
t.Errorf("errors encountered parsing getfacl output\n%s", errors.Join(c.pe...).Error())
|
|
||||||
}
|
|
||||||
return c.val
|
|
||||||
}
|
|
||||||
|
|
||||||
func respByCred(v []*getFAclResp, typ fAclType, cred int32) *getFAclResp {
|
|
||||||
j := -1
|
|
||||||
for i, r := range v {
|
|
||||||
if r.typ == typ && r.cred == cred {
|
|
||||||
if j != -1 {
|
|
||||||
panic("invalid acl")
|
|
||||||
}
|
|
||||||
j = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if j == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v[j]
|
|
||||||
}
|
|
163
acl/c.go
163
acl/c.go
@ -1,95 +1,50 @@
|
|||||||
package acl
|
package acl
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"fmt"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
//#include <stdlib.h>
|
||||||
#cgo linux pkg-config: libacl
|
//#include <sys/acl.h>
|
||||||
|
//#include <acl/libacl.h>
|
||||||
#include <stdlib.h>
|
//#cgo linux LDFLAGS: -lacl
|
||||||
#include <sys/acl.h>
|
|
||||||
#include <acl/libacl.h>
|
|
||||||
|
|
||||||
static acl_t _go_acl_get_file(const char *path_p, acl_type_t type) {
|
|
||||||
acl_t acl = acl_get_file(path_p, type);
|
|
||||||
free((void *)path_p);
|
|
||||||
return acl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _go_acl_set_file(const char *path_p, acl_type_t type, acl_t acl) {
|
|
||||||
if (acl_valid(acl) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = acl_set_file(path_p, type, acl);
|
|
||||||
free((void *)path_p);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
func getFile(name string, t C.acl_type_t) (*ACL, error) {
|
type acl struct {
|
||||||
a, err := C._go_acl_get_file(C.CString(name), t)
|
val C.acl_t
|
||||||
|
freed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func aclGetFile(path string, t C.acl_type_t) (*acl, error) {
|
||||||
|
p := C.CString(path)
|
||||||
|
a, err := C.acl_get_file(p, t)
|
||||||
|
C.free(unsafe.Pointer(p))
|
||||||
|
|
||||||
if errors.Is(err, syscall.ENODATA) {
|
if errors.Is(err, syscall.ENODATA) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
return &acl{val: a, freed: false}, err
|
||||||
return newACL(a), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acl *ACL) setFile(name string, t C.acl_type_t) error {
|
func (a *acl) setFile(path string, t C.acl_type_t) error {
|
||||||
_, err := C._go_acl_set_file(C.CString(name), t, acl.acl)
|
if C.acl_valid(a.val) != 0 {
|
||||||
|
return fmt.Errorf("invalid acl")
|
||||||
|
}
|
||||||
|
|
||||||
|
p := C.CString(path)
|
||||||
|
_, err := C.acl_set_file(p, t, a.val)
|
||||||
|
C.free(unsafe.Pointer(p))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newACL(a C.acl_t) *ACL {
|
func (a *acl) removeEntry(tt C.acl_tag_t, tq int) error {
|
||||||
acl := &ACL{a}
|
|
||||||
runtime.SetFinalizer(acl, (*ACL).free)
|
|
||||||
return acl
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACL struct {
|
|
||||||
acl C.acl_t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (acl *ACL) free() {
|
|
||||||
C.acl_free(unsafe.Pointer(acl.acl))
|
|
||||||
|
|
||||||
// no need for a finalizer anymore
|
|
||||||
runtime.SetFinalizer(acl, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Read = C.ACL_READ
|
|
||||||
Write = C.ACL_WRITE
|
|
||||||
Execute = C.ACL_EXECUTE
|
|
||||||
|
|
||||||
TypeDefault = C.ACL_TYPE_DEFAULT
|
|
||||||
TypeAccess = C.ACL_TYPE_ACCESS
|
|
||||||
|
|
||||||
UndefinedTag = C.ACL_UNDEFINED_TAG
|
|
||||||
UserObj = C.ACL_USER_OBJ
|
|
||||||
User = C.ACL_USER
|
|
||||||
GroupObj = C.ACL_GROUP_OBJ
|
|
||||||
Group = C.ACL_GROUP
|
|
||||||
Mask = C.ACL_MASK
|
|
||||||
Other = C.ACL_OTHER
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Perm C.acl_perm_t
|
|
||||||
)
|
|
||||||
|
|
||||||
func (acl *ACL) removeEntry(tt C.acl_tag_t, tq int) error {
|
|
||||||
var e C.acl_entry_t
|
var e C.acl_entry_t
|
||||||
|
|
||||||
// get first entry
|
// get first entry
|
||||||
if r, err := C.acl_get_entry(acl.acl, C.ACL_FIRST_ENTRY, &e); err != nil {
|
if r, err := C.acl_get_entry(a.val, C.ACL_FIRST_ENTRY, &e); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if r == 0 {
|
} else if r == 0 {
|
||||||
// return on acl with no entries
|
// return on acl with no entries
|
||||||
@ -97,7 +52,7 @@ func (acl *ACL) removeEntry(tt C.acl_tag_t, tq int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if r, err := C.acl_get_entry(acl.acl, C.ACL_NEXT_ENTRY, &e); err != nil {
|
if r, err := C.acl_get_entry(a.val, C.ACL_NEXT_ENTRY, &e); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if r == 0 {
|
} else if r == 0 {
|
||||||
// return on drained acl
|
// return on drained acl
|
||||||
@ -129,68 +84,16 @@ func (acl *ACL) removeEntry(tt C.acl_tag_t, tq int) error {
|
|||||||
|
|
||||||
// delete on match
|
// delete on match
|
||||||
if t == tt && q == tq {
|
if t == tt && q == tq {
|
||||||
_, err := C.acl_delete_entry(acl.acl, e)
|
_, err := C.acl_delete_entry(a.val, e)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePerm(name string, uid int, perms ...Perm) error {
|
func (a *acl) free() {
|
||||||
// read acl from file
|
if a.freed {
|
||||||
a, err := getFile(name, TypeAccess)
|
panic("acl already freed")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
// free acl on return if get is successful
|
C.acl_free(unsafe.Pointer(a.val))
|
||||||
defer a.free()
|
a.freed = true
|
||||||
|
|
||||||
// remove existing entry
|
|
||||||
if err = a.removeEntry(User, uid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new entry if perms are passed
|
|
||||||
if len(perms) > 0 {
|
|
||||||
// create new acl entry
|
|
||||||
var e C.acl_entry_t
|
|
||||||
if _, err = C.acl_create_entry(&a.acl, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get perm set of new entry
|
|
||||||
var p C.acl_permset_t
|
|
||||||
if _, err = C.acl_get_permset(e, &p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add target perms
|
|
||||||
for _, perm := range perms {
|
|
||||||
if _, err = C.acl_add_perm(p, C.acl_perm_t(perm)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set perm set to new entry
|
|
||||||
if _, err = C.acl_set_permset(e, p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set user tag to new entry
|
|
||||||
if _, err = C.acl_set_tag_type(e, User); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set qualifier (uid) to new entry
|
|
||||||
if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate mask after update
|
|
||||||
if _, err = C.acl_calc_mask(&a.acl); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// write acl to file
|
|
||||||
return a.setFile(name, TypeAccess)
|
|
||||||
}
|
}
|
||||||
|
107
acl/export.go
Normal file
107
acl/export.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Package acl implements simple ACL manipulation via libacl.
|
||||||
|
package acl
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
//#include <sys/acl.h>
|
||||||
|
//#include <acl/libacl.h>
|
||||||
|
//#cgo linux LDFLAGS: -lacl
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Read = C.ACL_READ
|
||||||
|
Write = C.ACL_WRITE
|
||||||
|
Execute = C.ACL_EXECUTE
|
||||||
|
|
||||||
|
TypeDefault = C.ACL_TYPE_DEFAULT
|
||||||
|
TypeAccess = C.ACL_TYPE_ACCESS
|
||||||
|
|
||||||
|
UndefinedTag = C.ACL_UNDEFINED_TAG
|
||||||
|
UserObj = C.ACL_USER_OBJ
|
||||||
|
User = C.ACL_USER
|
||||||
|
GroupObj = C.ACL_GROUP_OBJ
|
||||||
|
Group = C.ACL_GROUP
|
||||||
|
Mask = C.ACL_MASK
|
||||||
|
Other = C.ACL_OTHER
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Perm C.acl_perm_t
|
||||||
|
Perms []Perm
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ps Perms) String() string {
|
||||||
|
var s = []byte("---")
|
||||||
|
for _, p := range ps {
|
||||||
|
switch p {
|
||||||
|
case Read:
|
||||||
|
s[0] = 'r'
|
||||||
|
case Write:
|
||||||
|
s[1] = 'w'
|
||||||
|
case Execute:
|
||||||
|
s[2] = 'x'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePerm(path string, uid int, perms ...Perm) error {
|
||||||
|
// read acl from file
|
||||||
|
a, err := aclGetFile(path, TypeAccess)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// free acl on return if get is successful
|
||||||
|
defer a.free()
|
||||||
|
|
||||||
|
// remove existing entry
|
||||||
|
if err = a.removeEntry(User, uid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new entry if perms are passed
|
||||||
|
if len(perms) > 0 {
|
||||||
|
// create new acl entry
|
||||||
|
var e C.acl_entry_t
|
||||||
|
if _, err = C.acl_create_entry(&a.val, &e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get perm set of new entry
|
||||||
|
var p C.acl_permset_t
|
||||||
|
if _, err = C.acl_get_permset(e, &p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add target perms
|
||||||
|
for _, perm := range perms {
|
||||||
|
if _, err = C.acl_add_perm(p, C.acl_perm_t(perm)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set perm set to new entry
|
||||||
|
if _, err = C.acl_set_permset(e, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set user tag to new entry
|
||||||
|
if _, err = C.acl_set_tag_type(e, User); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set qualifier (uid) to new entry
|
||||||
|
if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate mask after update
|
||||||
|
if _, err = C.acl_calc_mask(&a.val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write acl to file
|
||||||
|
return a.setFile(path, TypeAccess)
|
||||||
|
}
|
@ -123,11 +123,6 @@ func main() {
|
|||||||
suppGroups = []int{uid}
|
suppGroups = []int{uid}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final bounds check to catch any bugs
|
|
||||||
if uid < 1000000 || uid >= 2000000 {
|
|
||||||
panic("uid out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
// careful! users in the allowlist is effectively allowed to drop groups via fsu
|
// careful! users in the allowlist is effectively allowed to drop groups via fsu
|
||||||
|
|
||||||
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
||||||
|
28
flake.lock
generated
28
flake.lock
generated
@ -1,33 +1,12 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"home-manager": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1733951536,
|
|
||||||
"narHash": "sha256-Zb5ZCa7Xj+0gy5XVXINTSr71fCfAv+IKtmIXNrykT54=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "1318c3f3b068cdcea922fa7c1a0a1f0c96c22f5f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "release-24.11",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1734298236,
|
"lastModified": 1733348545,
|
||||||
"narHash": "sha256-aWhhqY44xBjMoO9r5fyPp5u8tqUNWRZ/m/P+abMSs5c=",
|
"narHash": "sha256-b4JrUmqT0vFNx42aEN9LTWOHomkTKL/ayLopflVf81U=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "eb919d9300b6a18f8583f58aef16db458fbd7bec",
|
"rev": "9ecb50d2fae8680be74c08bb0a995c5383747f89",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -39,7 +18,6 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": "home-manager",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
62
flake.nix
62
flake.nix
@ -3,19 +3,10 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11-small";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11-small";
|
||||||
|
|
||||||
home-manager = {
|
|
||||||
url = "github:nix-community/home-manager/release-24.11";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{ self, nixpkgs }:
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
home-manager,
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
supportedSystems = [
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
@ -29,55 +20,6 @@
|
|||||||
{
|
{
|
||||||
nixosModules.fortify = import ./nixos.nix;
|
nixosModules.fortify = import ./nixos.nix;
|
||||||
|
|
||||||
checks = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = nixpkgsFor.${system};
|
|
||||||
|
|
||||||
inherit (pkgs)
|
|
||||||
runCommandLocal
|
|
||||||
callPackage
|
|
||||||
nixfmt-rfc-style
|
|
||||||
deadnix
|
|
||||||
statix
|
|
||||||
;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
check-formatting =
|
|
||||||
runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; }
|
|
||||||
''
|
|
||||||
cd ${./.}
|
|
||||||
|
|
||||||
echo "running nixfmt..."
|
|
||||||
nixfmt --check .
|
|
||||||
|
|
||||||
touch $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
check-lint =
|
|
||||||
runCommandLocal "check-lint"
|
|
||||||
{
|
|
||||||
nativeBuildInputs = [
|
|
||||||
deadnix
|
|
||||||
statix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
cd ${./.}
|
|
||||||
|
|
||||||
echo "running deadnix..."
|
|
||||||
deadnix --fail
|
|
||||||
|
|
||||||
echo "running statix..."
|
|
||||||
statix check .
|
|
||||||
|
|
||||||
touch $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
nixos-tests = callPackage ./test.nix { inherit self home-manager; };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
@ -114,7 +56,7 @@
|
|||||||
};
|
};
|
||||||
modules = [ ./options.nix ];
|
modules = [ ./options.nix ];
|
||||||
};
|
};
|
||||||
cleanEval = lib.filterAttrsRecursive (n: _: n != "_module") eval;
|
cleanEval = lib.filterAttrsRecursive (n: v: n != "_module") eval;
|
||||||
in
|
in
|
||||||
pkgs.nixosOptionsDoc { inherit (cleanEval) options; };
|
pkgs.nixosOptionsDoc { inherit (cleanEval) options; };
|
||||||
docText = pkgs.runCommand "fortify-module-docs.md" { } ''
|
docText = pkgs.runCommand "fortify-module-docs.md" { } ''
|
||||||
|
@ -10,6 +10,7 @@ let
|
|||||||
mkIf
|
mkIf
|
||||||
mkDefault
|
mkDefault
|
||||||
mapAttrs
|
mapAttrs
|
||||||
|
mapAttrsToList
|
||||||
mergeAttrsList
|
mergeAttrsList
|
||||||
imap1
|
imap1
|
||||||
foldr
|
foldr
|
||||||
|
@ -31,6 +31,7 @@ in
|
|||||||
let
|
let
|
||||||
inherit (types)
|
inherit (types)
|
||||||
str
|
str
|
||||||
|
enum
|
||||||
bool
|
bool
|
||||||
package
|
package
|
||||||
anything
|
anything
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "0.2.3";
|
version = "0.2.2";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
@ -43,9 +43,6 @@ buildGoModule rec {
|
|||||||
Finit = "${placeholder "out"}/libexec/finit";
|
Finit = "${placeholder "out"}/libexec/finit";
|
||||||
};
|
};
|
||||||
|
|
||||||
# nix build environment does not allow acls
|
|
||||||
GO_TEST_SKIP_ACL = 1;
|
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
acl
|
acl
|
||||||
wayland
|
wayland
|
||||||
|
195
test.nix
195
test.nix
@ -1,195 +0,0 @@
|
|||||||
{
|
|
||||||
self,
|
|
||||||
home-manager,
|
|
||||||
nixosTest,
|
|
||||||
}:
|
|
||||||
|
|
||||||
nixosTest {
|
|
||||||
name = "fortify";
|
|
||||||
|
|
||||||
# adapted from nixos sway integration tests
|
|
||||||
|
|
||||||
# testScriptWithTypes:49: error: Cannot call function of unknown type
|
|
||||||
# (machine.succeed if succeed else machine.execute)(
|
|
||||||
# ^
|
|
||||||
# Found 1 error in 1 file (checked 1 source file)
|
|
||||||
skipTypeCheck = true;
|
|
||||||
|
|
||||||
nodes.machine =
|
|
||||||
{ lib, pkgs, ... }:
|
|
||||||
{
|
|
||||||
users.users.alice = {
|
|
||||||
isNormalUser = true;
|
|
||||||
description = "Alice Foobar";
|
|
||||||
password = "foobar";
|
|
||||||
uid = 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.alice.home.stateVersion = "24.11";
|
|
||||||
|
|
||||||
# Automatically login on tty1 as a normal user:
|
|
||||||
services.getty.autologinUser = "alice";
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
# For glinfo and wayland-info:
|
|
||||||
systemPackages = with pkgs; [
|
|
||||||
mesa-demos
|
|
||||||
wayland-utils
|
|
||||||
alacritty
|
|
||||||
];
|
|
||||||
|
|
||||||
variables = {
|
|
||||||
SWAYSOCK = "/tmp/sway-ipc.sock";
|
|
||||||
WLR_RENDERER = "pixman";
|
|
||||||
};
|
|
||||||
|
|
||||||
# To help with OCR:
|
|
||||||
etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
|
|
||||||
main = {
|
|
||||||
font = "inconsolata:size=14";
|
|
||||||
};
|
|
||||||
colors = rec {
|
|
||||||
foreground = "000000";
|
|
||||||
background = "ffffff";
|
|
||||||
regular2 = foreground;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fonts.packages = [ pkgs.inconsolata ];
|
|
||||||
|
|
||||||
# Automatically configure and start Sway when logging in on tty1:
|
|
||||||
programs.bash.loginShellInit = ''
|
|
||||||
if [ "$(tty)" = "/dev/tty1" ]; then
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mkdir -p ~/.config/sway
|
|
||||||
sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
|
|
||||||
|
|
||||||
sway --validate
|
|
||||||
sway && touch /tmp/sway-exit-ok
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
programs.sway.enable = true;
|
|
||||||
|
|
||||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
|
||||||
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
|
|
||||||
|
|
||||||
environment.fortify = {
|
|
||||||
enable = true;
|
|
||||||
stateDir = "/var/lib/fortify";
|
|
||||||
users.alice = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.fortify
|
|
||||||
home-manager.nixosModules.home-manager
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
import shlex
|
|
||||||
import json
|
|
||||||
|
|
||||||
q = shlex.quote
|
|
||||||
NODE_GROUPS = ["nodes", "floating_nodes"]
|
|
||||||
|
|
||||||
|
|
||||||
def swaymsg(command: str = "", succeed=True, type="command"):
|
|
||||||
assert command != "" or type != "command", "Must specify command or type"
|
|
||||||
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
|
||||||
with machine.nested(
|
|
||||||
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
|
|
||||||
):
|
|
||||||
ret = (machine.succeed if succeed else machine.execute)(
|
|
||||||
f"su - alice -c {shell}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# execute also returns a status code, but disregard.
|
|
||||||
if not succeed:
|
|
||||||
_, ret = ret
|
|
||||||
|
|
||||||
if not succeed and not ret:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parsed = json.loads(ret)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def walk(tree):
|
|
||||||
yield tree
|
|
||||||
for group in NODE_GROUPS:
|
|
||||||
for node in tree.get(group, []):
|
|
||||||
yield from walk(node)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_window(pattern):
|
|
||||||
def func(last_chance):
|
|
||||||
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
|
|
||||||
|
|
||||||
if last_chance:
|
|
||||||
nodes = list(nodes)
|
|
||||||
machine.log(f"Last call! Current list of windows: {nodes}")
|
|
||||||
|
|
||||||
return any(pattern in name for name in nodes)
|
|
||||||
|
|
||||||
retry(func)
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("multi-user.target")
|
|
||||||
|
|
||||||
# To check the version:
|
|
||||||
print(machine.succeed("sway --version"))
|
|
||||||
|
|
||||||
# Wait for Sway to complete startup:
|
|
||||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
|
||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
|
||||||
|
|
||||||
# Create fortify aid 0 home directory:
|
|
||||||
machine.succeed("install -dm 0700 -o 1000000 -g 1000000 /var/lib/fortify/u0/a0")
|
|
||||||
|
|
||||||
# Start fortify outside Wayland session:
|
|
||||||
print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare"))
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare")
|
|
||||||
|
|
||||||
# Start fortify within Wayland session:
|
|
||||||
swaymsg("exec fortify -v run --wayland touch /tmp/success-session")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-session")
|
|
||||||
|
|
||||||
# Start a terminal (foot) within fortify on workspace 3:
|
|
||||||
machine.send_key("alt-3")
|
|
||||||
machine.sleep(3)
|
|
||||||
swaymsg("exec fortify run --wayland foot")
|
|
||||||
wait_for_window("u0_a0@machine")
|
|
||||||
machine.send_chars("wayland-info && touch /tmp/success-client\n")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client")
|
|
||||||
machine.screenshot("foot_wayland_permissive")
|
|
||||||
machine.send_chars("exit\n")
|
|
||||||
machine.wait_until_fails("pgrep foot")
|
|
||||||
|
|
||||||
# Start a terminal (foot) within fortify from a terminal on workspace 4:
|
|
||||||
machine.send_key("alt-4")
|
|
||||||
machine.sleep(3)
|
|
||||||
swaymsg("exec foot fortify run --wayland foot")
|
|
||||||
wait_for_window("u0_a0@machine")
|
|
||||||
machine.send_chars("wayland-info && touch /tmp/success-client-term\n")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client-term")
|
|
||||||
machine.screenshot("foot_wayland_permissive_term")
|
|
||||||
machine.send_chars("exit\n")
|
|
||||||
machine.wait_until_fails("pgrep foot")
|
|
||||||
|
|
||||||
# Test XWayland (foot does not support X):
|
|
||||||
swaymsg("exec fortify run -X alacritty")
|
|
||||||
wait_for_window("u0_a0@machine")
|
|
||||||
machine.send_chars("glinfo && touch /tmp/success-client-x11\n")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-client-x11")
|
|
||||||
machine.screenshot("alacritty_x11_permissive")
|
|
||||||
machine.succeed("pkill alacritty")
|
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
|
||||||
swaymsg("exit", succeed=False)
|
|
||||||
machine.wait_until_fails("pgrep -x sway")
|
|
||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
|
||||||
'';
|
|
||||||
}
|
|
131
xcb/c.go
131
xcb/c.go
@ -1,124 +1,33 @@
|
|||||||
package xcb
|
package xcb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"errors"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
//#include <stdlib.h>
|
||||||
#cgo linux pkg-config: xcb
|
//#include <xcb/xcb.h>
|
||||||
|
//#cgo linux LDFLAGS: -lxcb
|
||||||
#include <stdlib.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
|
|
||||||
static int _go_xcb_change_hosts_checked(xcb_connection_t *c, uint8_t mode, uint8_t family, uint16_t address_len, const uint8_t *address) {
|
|
||||||
xcb_void_cookie_t cookie = xcb_change_hosts_checked(c, mode, family, address_len, address);
|
|
||||||
free((void *)address);
|
|
||||||
|
|
||||||
int errno = xcb_connection_has_error(c);
|
|
||||||
if (errno != 0)
|
|
||||||
return errno;
|
|
||||||
|
|
||||||
xcb_generic_error_t *e = xcb_request_check(c, cookie);
|
|
||||||
if (e != NULL) {
|
|
||||||
// don't want to deal with xcb errors
|
|
||||||
free((void *)e);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
const (
|
func xcbHandleConnectionError(c *C.xcb_connection_t) error {
|
||||||
HostModeInsert = C.XCB_HOST_MODE_INSERT
|
if errno := C.xcb_connection_has_error(c); errno != 0 {
|
||||||
HostModeDelete = C.XCB_HOST_MODE_DELETE
|
|
||||||
|
|
||||||
FamilyInternet = C.XCB_FAMILY_INTERNET
|
|
||||||
FamilyDecnet = C.XCB_FAMILY_DECNET
|
|
||||||
FamilyChaos = C.XCB_FAMILY_CHAOS
|
|
||||||
FamilyServerInterpreted = C.XCB_FAMILY_SERVER_INTERPRETED
|
|
||||||
FamilyInternet6 = C.XCB_FAMILY_INTERNET_6
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
HostMode = C.xcb_host_mode_t
|
|
||||||
Family = C.xcb_family_t
|
|
||||||
)
|
|
||||||
|
|
||||||
func (conn *connection) changeHostsChecked(mode HostMode, family Family, address string) error {
|
|
||||||
errno := C._go_xcb_change_hosts_checked(
|
|
||||||
conn.c,
|
|
||||||
C.uint8_t(mode),
|
|
||||||
C.uint8_t(family),
|
|
||||||
C.uint16_t(len(address)),
|
|
||||||
(*C.uint8_t)(unsafe.Pointer(C.CString(address))),
|
|
||||||
)
|
|
||||||
switch errno {
|
switch errno {
|
||||||
case 0:
|
case C.XCB_CONN_ERROR:
|
||||||
return nil
|
return errors.New("connection error")
|
||||||
case -1:
|
case C.XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
|
||||||
return ErrChangeHosts
|
return errors.New("extension not supported")
|
||||||
|
case C.XCB_CONN_CLOSED_MEM_INSUFFICIENT:
|
||||||
|
return errors.New("memory not available")
|
||||||
|
case C.XCB_CONN_CLOSED_REQ_LEN_EXCEED:
|
||||||
|
return errors.New("request length exceeded")
|
||||||
|
case C.XCB_CONN_CLOSED_PARSE_ERR:
|
||||||
|
return errors.New("invalid display string")
|
||||||
|
case C.XCB_CONN_CLOSED_INVALID_SCREEN:
|
||||||
|
return errors.New("server has no screen matching display")
|
||||||
default:
|
default:
|
||||||
return &ConnectionError{errno}
|
return errors.New("generic X11 failure")
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
type connection struct{ c *C.xcb_connection_t }
|
|
||||||
|
|
||||||
func connect() (*connection, error) {
|
|
||||||
conn := newConnection(C.xcb_connect(nil, nil))
|
|
||||||
return conn, conn.hasError()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnection(c *C.xcb_connection_t) *connection {
|
|
||||||
conn := &connection{c}
|
|
||||||
runtime.SetFinalizer(conn, (*connection).disconnect)
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ConnError = C.XCB_CONN_ERROR
|
|
||||||
ConnClosedExtNotSupported = C.XCB_CONN_CLOSED_EXT_NOTSUPPORTED
|
|
||||||
ConnClosedMemInsufficient = C.XCB_CONN_CLOSED_MEM_INSUFFICIENT
|
|
||||||
ConnClosedReqLenExceed = C.XCB_CONN_CLOSED_REQ_LEN_EXCEED
|
|
||||||
ConnClosedParseErr = C.XCB_CONN_CLOSED_PARSE_ERR
|
|
||||||
ConnClosedInvalidScreen = C.XCB_CONN_CLOSED_INVALID_SCREEN
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnectionError struct{ errno C.int }
|
|
||||||
|
|
||||||
func (ce *ConnectionError) Error() string {
|
|
||||||
switch ce.errno {
|
|
||||||
case ConnError:
|
|
||||||
return "connection error"
|
|
||||||
case ConnClosedExtNotSupported:
|
|
||||||
return "extension not supported"
|
|
||||||
case ConnClosedMemInsufficient:
|
|
||||||
return "memory not available"
|
|
||||||
case ConnClosedReqLenExceed:
|
|
||||||
return "request length exceeded"
|
|
||||||
case ConnClosedParseErr:
|
|
||||||
return "invalid display string"
|
|
||||||
case ConnClosedInvalidScreen:
|
|
||||||
return "server has no screen matching display"
|
|
||||||
default:
|
|
||||||
return "generic X11 failure"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *connection) hasError() error {
|
|
||||||
errno := C.xcb_connection_has_error(conn.c)
|
|
||||||
if errno == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &ConnectionError{errno}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *connection) disconnect() {
|
|
||||||
C.xcb_disconnect(conn.c)
|
|
||||||
|
|
||||||
// no need for a finalizer anymore
|
|
||||||
runtime.SetFinalizer(conn, nil)
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,63 @@
|
|||||||
// Package xcb implements X11 ChangeHosts via libxcb.
|
// Package xcb implements X11 ChangeHosts via libxcb.
|
||||||
package xcb
|
package xcb
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
//#include <xcb/xcb.h>
|
||||||
|
//#cgo linux LDFLAGS: -lxcb
|
||||||
|
import "C"
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrChangeHosts = errors.New("xcb_change_hosts() failed")
|
const (
|
||||||
|
HostModeInsert = C.XCB_HOST_MODE_INSERT
|
||||||
|
HostModeDelete = C.XCB_HOST_MODE_DELETE
|
||||||
|
|
||||||
func ChangeHosts(mode HostMode, family Family, address string) error {
|
FamilyInternet = C.XCB_FAMILY_INTERNET
|
||||||
var conn *connection
|
FamilyDecnet = C.XCB_FAMILY_DECNET
|
||||||
|
FamilyChaos = C.XCB_FAMILY_CHAOS
|
||||||
|
FamilyServerInterpreted = C.XCB_FAMILY_SERVER_INTERPRETED
|
||||||
|
FamilyInternet6 = C.XCB_FAMILY_INTERNET_6
|
||||||
|
)
|
||||||
|
|
||||||
if c, err := connect(); err != nil {
|
type ConnectionError struct {
|
||||||
c.disconnect()
|
err error
|
||||||
return err
|
}
|
||||||
} else {
|
|
||||||
defer c.disconnect()
|
func (e *ConnectionError) Error() string {
|
||||||
conn = c
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ConnectionError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrChangeHosts = errors.New("xcb_change_hosts() failed")
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChangeHosts(mode, family C.uint8_t, address string) error {
|
||||||
|
c := C.xcb_connect(nil, nil)
|
||||||
|
defer C.xcb_disconnect(c)
|
||||||
|
|
||||||
|
if err := xcbHandleConnectionError(c); err != nil {
|
||||||
|
return &ConnectionError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn.changeHostsChecked(mode, family, address)
|
addr := C.CString(address)
|
||||||
|
cookie := C.xcb_change_hosts_checked(c, mode, family, C.ushort(len(address)), (*C.uchar)(unsafe.Pointer(addr)))
|
||||||
|
C.free(unsafe.Pointer(addr))
|
||||||
|
|
||||||
|
if err := xcbHandleConnectionError(c); err != nil {
|
||||||
|
return &ConnectionError{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := C.xcb_request_check(c, cookie)
|
||||||
|
if e != nil {
|
||||||
|
defer C.free(unsafe.Pointer(e))
|
||||||
|
return ErrChangeHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user