ctlsock: add initial limited implementation
At the moment, in forward mode you can only encrypt paths and in reverse mode you can only decrypt paths.
This commit is contained in:
parent
df28fc5a11
commit
75ebb28a62
@ -18,7 +18,7 @@ type argContainer struct {
|
|||||||
plaintextnames, quiet, nosyslog, wpanic,
|
plaintextnames, quiet, nosyslog, wpanic,
|
||||||
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool
|
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool
|
||||||
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
|
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
|
||||||
memprofile, ko, passfile string
|
memprofile, ko, passfile, ctlsock string
|
||||||
// Configuration file name override
|
// Configuration file name override
|
||||||
config string
|
config string
|
||||||
notifypid, scryptn int
|
notifypid, scryptn int
|
||||||
@ -112,6 +112,7 @@ func parseCliOpts() (args argContainer) {
|
|||||||
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")
|
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")
|
||||||
flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file")
|
flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file")
|
||||||
flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")
|
flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")
|
||||||
|
flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path")
|
||||||
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||||
"successful mount - used internally for daemonization")
|
"successful mount - used internally for daemonization")
|
||||||
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+
|
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+
|
||||||
|
137
internal/ctlsock/ctlsock_serve.go
Normal file
137
internal/ctlsock/ctlsock_serve.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// Package ctlsock implementes the control socket interface that can be
|
||||||
|
// activated by passing "-ctlsock" on the command line.
|
||||||
|
package ctlsock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface should be implemented by fusefrontend[_reverse]
|
||||||
|
type Interface interface {
|
||||||
|
EncryptPath(string) (string, error)
|
||||||
|
DecryptPath(string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestStruct is sent by a client
|
||||||
|
type RequestStruct struct {
|
||||||
|
EncryptPath string
|
||||||
|
DecryptPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseStruct is sent by us as response to a request
|
||||||
|
type ResponseStruct struct {
|
||||||
|
// Result is the resulting decrypted or encrypted path. Empty on error.
|
||||||
|
Result string
|
||||||
|
// ErrNo is the error number as defined in errno.h.
|
||||||
|
// 0 means success and -1 means that the error number is not known
|
||||||
|
// (look at ErrText in this case).
|
||||||
|
ErrNo int32
|
||||||
|
// ErrText is a detailed error message.
|
||||||
|
ErrText string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctlSockHandler struct {
|
||||||
|
fs Interface
|
||||||
|
socket *net.UnixListener
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAndServe creates an unix socket at "path" and serves incoming
|
||||||
|
// connections in a new goroutine.
|
||||||
|
func CreateAndServe(path string, fs Interface) error {
|
||||||
|
sock, err := net.Listen("unix", path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handler := ctlSockHandler{
|
||||||
|
fs: fs,
|
||||||
|
socket: sock.(*net.UnixListener),
|
||||||
|
}
|
||||||
|
go handler.acceptLoop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ctlSockHandler) acceptLoop() {
|
||||||
|
for {
|
||||||
|
conn, err := ch.socket.Accept()
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("ctlsock: Accept error: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go ch.handleConnection(conn.(*net.UnixConn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) {
|
||||||
|
// 2*PATH_MAX is definitely big enough for requests to decrypt or
|
||||||
|
// encrypt paths.
|
||||||
|
buf := make([]byte, 2*syscall.PathMax)
|
||||||
|
for {
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err == io.EOF {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
tlog.Warn.Printf("ctlsock: Read error: %#v", err)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf = buf[:n]
|
||||||
|
var in RequestStruct
|
||||||
|
err = json.Unmarshal(buf, &in)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("ctlsock: Unmarshal error: %#v", err)
|
||||||
|
errorMsg := ResponseStruct{
|
||||||
|
ErrNo: int32(syscall.EINVAL),
|
||||||
|
ErrText: err.Error(),
|
||||||
|
}
|
||||||
|
sendResponse(&errorMsg, conn)
|
||||||
|
}
|
||||||
|
ch.handleRequest(&in, conn)
|
||||||
|
// Restore original size.
|
||||||
|
buf = buf[:cap(buf)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) {
|
||||||
|
var err error
|
||||||
|
var out ResponseStruct
|
||||||
|
if in.DecryptPath != "" && in.EncryptPath != "" {
|
||||||
|
err = errors.New("Ambigous")
|
||||||
|
} else if in.DecryptPath == "" && in.EncryptPath == "" {
|
||||||
|
err = errors.New("No operation")
|
||||||
|
} else if in.DecryptPath != "" {
|
||||||
|
out.Result, err = ch.fs.DecryptPath(in.DecryptPath)
|
||||||
|
} else if in.EncryptPath != "" {
|
||||||
|
out.Result, err = ch.fs.EncryptPath(in.EncryptPath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
out.ErrText = err.Error()
|
||||||
|
out.ErrNo = -1
|
||||||
|
// Try to extract the actual error number
|
||||||
|
if pe, ok := err.(*os.PathError); ok {
|
||||||
|
if se, ok := pe.Err.(syscall.Errno); ok {
|
||||||
|
out.ErrNo = int32(se)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendResponse(&out, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendResponse(msg *ResponseStruct, conn *net.UnixConn) {
|
||||||
|
jsonMsg, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("ctlsock: Marshal failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = conn.Write(jsonMsg)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("ctlsock: Write failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
19
internal/fusefrontend/ctlsock_interface.go
Normal file
19
internal/fusefrontend/ctlsock_interface.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package fusefrontend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ctlsock.Interface = &FS{} // Verify that interface is implemented.
|
||||||
|
|
||||||
|
// EncryptPath implements ctlsock.Backend
|
||||||
|
func (fs *FS) EncryptPath(plainPath string) (string, error) {
|
||||||
|
return fs.encryptPath(plainPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptPath implements ctlsock.Backend
|
||||||
|
func (fs *FS) DecryptPath(plainPath string) (string, error) {
|
||||||
|
return "", errors.New("Not implemented")
|
||||||
|
}
|
@ -21,7 +21,7 @@ var wlock wlockMap
|
|||||||
// 2) lock ... unlock ...
|
// 2) lock ... unlock ...
|
||||||
// 3) unregister
|
// 3) unregister
|
||||||
type wlockMap struct {
|
type wlockMap struct {
|
||||||
// Counts lock() calls. As every operation that modifies a file should
|
// opCount counts lock() calls. As every operation that modifies a file should
|
||||||
// call it, this effectively serves as a write-operation counter.
|
// call it, this effectively serves as a write-operation counter.
|
||||||
// The variable is accessed without holding any locks so atomic operations
|
// The variable is accessed without holding any locks so atomic operations
|
||||||
// must be used. It must be the first element of the struct to guarantee
|
// must be used. It must be the first element of the struct to guarantee
|
||||||
|
19
internal/fusefrontend_reverse/ctlsock_interface.go
Normal file
19
internal/fusefrontend_reverse/ctlsock_interface.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package fusefrontend_reverse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ctlsock.Interface = &reverseFS{} // Verify that interface is implemented.
|
||||||
|
|
||||||
|
// EncryptPath implements ctlsock.Backend
|
||||||
|
func (rfs *reverseFS) EncryptPath(plainPath string) (string, error) {
|
||||||
|
return "", errors.New("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptPath implements ctlsock.Backend
|
||||||
|
func (rfs *reverseFS) DecryptPath(plainPath string) (string, error) {
|
||||||
|
return rfs.decryptPath(plainPath)
|
||||||
|
}
|
@ -49,7 +49,7 @@ var _ pathfs.FileSystem = &reverseFS{}
|
|||||||
// NewFS returns an encrypted FUSE overlay filesystem.
|
// NewFS returns an encrypted FUSE overlay filesystem.
|
||||||
// In this case (reverse mode) the backing directory is plain-text and
|
// In this case (reverse mode) the backing directory is plain-text and
|
||||||
// reverseFS provides an encrypted view.
|
// reverseFS provides an encrypted view.
|
||||||
func NewFS(args fusefrontend.Args) pathfs.FileSystem {
|
func NewFS(args fusefrontend.Args) *reverseFS {
|
||||||
if args.CryptoBackend != cryptocore.BackendAESSIV {
|
if args.CryptoBackend != cryptocore.BackendAESSIV {
|
||||||
panic("reverse mode must use AES-SIV, everything else is insecure")
|
panic("reverse mode must use AES-SIV, everything else is insecure")
|
||||||
}
|
}
|
||||||
|
16
mount.go
16
mount.go
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse"
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
@ -165,10 +166,18 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
|||||||
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
|
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
|
||||||
tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
|
tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
|
||||||
var finalFs pathfs.FileSystem
|
var finalFs pathfs.FileSystem
|
||||||
|
var ctlSockBackend ctlsock.Interface
|
||||||
if args.reverse {
|
if args.reverse {
|
||||||
finalFs = fusefrontend_reverse.NewFS(frontendArgs)
|
fs := fusefrontend_reverse.NewFS(frontendArgs)
|
||||||
|
finalFs = fs
|
||||||
|
ctlSockBackend = fs
|
||||||
} else {
|
} else {
|
||||||
finalFs = fusefrontend.NewFS(frontendArgs)
|
fs := fusefrontend.NewFS(frontendArgs)
|
||||||
|
finalFs = fs
|
||||||
|
ctlSockBackend = fs
|
||||||
|
}
|
||||||
|
if args.ctlsock != "" {
|
||||||
|
ctlsock.CreateAndServe(args.ctlsock, ctlSockBackend)
|
||||||
}
|
}
|
||||||
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
|
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
|
||||||
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
|
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
|
||||||
@ -200,9 +209,8 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
|||||||
if args.reverse {
|
if args.reverse {
|
||||||
mOpts.Name += "-reverse"
|
mOpts.Name += "-reverse"
|
||||||
}
|
}
|
||||||
|
|
||||||
// The kernel enforces read-only operation, we just have to pass "ro".
|
// The kernel enforces read-only operation, we just have to pass "ro".
|
||||||
// Reverse mounts are always read-only
|
// Reverse mounts are always read-only.
|
||||||
if args.ro || args.reverse {
|
if args.ro || args.reverse {
|
||||||
mOpts.Options = append(mOpts.Options, "ro")
|
mOpts.Options = append(mOpts.Options, "ro")
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,16 @@
|
|||||||
package defaults
|
package defaults
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,3 +41,34 @@ func Test1980Tar(t *testing.T) {
|
|||||||
t.Errorf("Wrong mtime: %d", m)
|
t.Errorf("Wrong mtime: %d", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCtlSock(t *testing.T) {
|
||||||
|
cDir := test_helpers.InitFS(t)
|
||||||
|
pDir := cDir + ".mnt"
|
||||||
|
sock := cDir + ".sock"
|
||||||
|
test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test")
|
||||||
|
defer test_helpers.UnmountPanic(pDir)
|
||||||
|
conn, err := net.DialTimeout("unix", sock, 1*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Second))
|
||||||
|
msg := []byte(`{"EncryptPath": "foobar"}`)
|
||||||
|
_, err = conn.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf := make([]byte, 2*syscall.PathMax)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf = buf[:n]
|
||||||
|
var response ctlsock.ResponseStruct
|
||||||
|
json.Unmarshal(buf, &response)
|
||||||
|
if response.Result == "" || response.ErrNo != 0 {
|
||||||
|
fmt.Printf("%s\n", string(buf))
|
||||||
|
t.Errorf("got an error reply")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user