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,
|
||||
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool
|
||||
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
|
||||
memprofile, ko, passfile string
|
||||
memprofile, ko, passfile, ctlsock string
|
||||
// Configuration file name override
|
||||
config string
|
||||
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.passfile, "passfile", "", "Read password from file")
|
||||
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 "+
|
||||
"successful mount - used internally for daemonization")
|
||||
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 ...
|
||||
// 3) unregister
|
||||
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.
|
||||
// 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
|
||||
|
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.
|
||||
// In this case (reverse mode) the backing directory is plain-text and
|
||||
// reverseFS provides an encrypted view.
|
||||
func NewFS(args fusefrontend.Args) pathfs.FileSystem {
|
||||
func NewFS(args fusefrontend.Args) *reverseFS {
|
||||
if args.CryptoBackend != cryptocore.BackendAESSIV {
|
||||
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/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
@ -165,10 +166,18 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
||||
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
|
||||
tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
|
||||
var finalFs pathfs.FileSystem
|
||||
var ctlSockBackend ctlsock.Interface
|
||||
if args.reverse {
|
||||
finalFs = fusefrontend_reverse.NewFS(frontendArgs)
|
||||
fs := fusefrontend_reverse.NewFS(frontendArgs)
|
||||
finalFs = fs
|
||||
ctlSockBackend = fs
|
||||
} 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}
|
||||
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
|
||||
@ -200,9 +209,8 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
||||
if args.reverse {
|
||||
mOpts.Name += "-reverse"
|
||||
}
|
||||
|
||||
// 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 {
|
||||
mOpts.Options = append(mOpts.Options, "ro")
|
||||
}
|
||||
|
@ -2,10 +2,16 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||
)
|
||||
|
||||
@ -35,3 +41,34 @@ func Test1980Tar(t *testing.T) {
|
||||
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