summaryrefslogtreecommitdiff
path: root/internal/ctlsock/ctlsock_serve.go
blob: 7e603017116a1213eec333c43682ee1093e8e381 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Package ctlsock implementes the control socket interface that can be
// activated by passing "-ctlsock" on the command line.
package ctlsock

import (
	"encoding/json"
	"errors"
	"fmt"
	"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
	// WarnText contains warnings that may have been encountered while
	// processing the message.
	WarnText string
}

type ctlSockHandler struct {
	fs     Interface
	socket *net.UnixListener
}

// Serve serves incoming connections on "sock". This call blocks so you
// probably want to run it in a new goroutine.
func Serve(sock net.Listener, fs Interface) {
	handler := ctlSockHandler{
		fs:     fs,
		socket: sock.(*net.UnixListener),
	}
	handler.acceptLoop()
}

func (ch *ctlSockHandler) acceptLoop() {
	for {
		conn, err := ch.socket.Accept()
		if err != nil {
			// TODO Can this warning trigger when the socket it closed on
			// program exit? I have never observed it, but the documentation
			// says that Close() unblocks Accept().
			tlog.Warn.Printf("ctlsock: Accept error: %v", err)
			break
		}
		go ch.handleConnection(conn.(*net.UnixConn))
	}
}

// The longest possible path is 4096 bytes on Linux and 1024 on Mac OS X so
// 5000 bytes should be enough to hold the whole JSON request. This
// assumes that the path does not contain too many characters that had to be
// be escaped in JSON (for example, a null byte blows up to "\u0000").
// We abort the connection if the request is bigger than this.
const ReadBufSize = 5000

func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) {
	buf := make([]byte, ReadBufSize)
	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
		}
		if n == ReadBufSize {
			tlog.Warn.Printf("ctlsock: request too big (max = %d bytes)", ReadBufSize-1)
			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
	var inPath, clean string
	if in.DecryptPath != "" && in.EncryptPath != "" {
		err = errors.New("Ambigous")
	} else if in.DecryptPath == "" && in.EncryptPath == "" {
		err = errors.New("No operation")
	} else if in.DecryptPath != "" {
		inPath = in.DecryptPath
		clean = SanitizePath(inPath)
		out.Result, err = ch.fs.DecryptPath(clean)
	} else if in.EncryptPath != "" {
		inPath = in.EncryptPath
		clean = SanitizePath(inPath)
		out.Result, err = ch.fs.EncryptPath(clean)
	}
	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)
			}
		}
	}
	if inPath != clean {
		out.WarnText = fmt.Sprintf("Non-canonical input path %q has been interpreted as %q", inPath, clean)
	}
	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
	}
	// For convenience for the user, add a newline at the end.
	jsonMsg = append(jsonMsg, '\n')
	_, err = conn.Write(jsonMsg)
	if err != nil {
		tlog.Warn.Printf("ctlsock: Write failed: %v", err)
	}
}