summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli_args.go3
-rw-r--r--internal/ctlsock/ctlsock_serve.go137
-rw-r--r--internal/fusefrontend/ctlsock_interface.go19
-rw-r--r--internal/fusefrontend/write_lock.go2
-rw-r--r--internal/fusefrontend_reverse/ctlsock_interface.go19
-rw-r--r--internal/fusefrontend_reverse/rfs.go2
-rw-r--r--mount.go16
-rw-r--r--tests/defaults/main_test.go37
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")
}
diff --git a/mount.go b/mount.go
index 1a00bb7..3bafd8c 100644
--- a/mount.go
+++ b/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")
}
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")
+ }
+}