diff options
| -rw-r--r-- | cli_args.go | 3 | ||||
| -rw-r--r-- | internal/ctlsock/ctlsock_serve.go | 137 | ||||
| -rw-r--r-- | internal/fusefrontend/ctlsock_interface.go | 19 | ||||
| -rw-r--r-- | internal/fusefrontend/write_lock.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/ctlsock_interface.go | 19 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 2 | ||||
| -rw-r--r-- | mount.go | 16 | ||||
| -rw-r--r-- | tests/defaults/main_test.go | 37 | 
8 files changed, 228 insertions, 7 deletions
| diff --git a/cli_args.go b/cli_args.go index 358a6a9..5751f81 100644 --- a/cli_args.go +++ b/cli_args.go @@ -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. "+ diff --git a/internal/ctlsock/ctlsock_serve.go b/internal/ctlsock/ctlsock_serve.go new file mode 100644 index 0000000..571260d --- /dev/null +++ b/internal/ctlsock/ctlsock_serve.go @@ -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) +	} +} diff --git a/internal/fusefrontend/ctlsock_interface.go b/internal/fusefrontend/ctlsock_interface.go new file mode 100644 index 0000000..c22dce6 --- /dev/null +++ b/internal/fusefrontend/ctlsock_interface.go @@ -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") +} diff --git a/internal/fusefrontend/write_lock.go b/internal/fusefrontend/write_lock.go index 7394994..3addfd6 100644 --- a/internal/fusefrontend/write_lock.go +++ b/internal/fusefrontend/write_lock.go @@ -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 diff --git a/internal/fusefrontend_reverse/ctlsock_interface.go b/internal/fusefrontend_reverse/ctlsock_interface.go new file mode 100644 index 0000000..448663f --- /dev/null +++ b/internal/fusefrontend_reverse/ctlsock_interface.go @@ -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) +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 87a2602..84348f7 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -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")  	} @@ -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")  	} diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go index 849aa69..8733c8b 100644 --- a/tests/defaults/main_test.go +++ b/tests/defaults/main_test.go @@ -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") +	} +} | 
