mirror of
https://github.com/rwinkhart/rcw.git
synced 2026-08-27 20:36:30 -04:00
Incomplete Windows daemon support
This commit is contained in:
+1
-3
@@ -2,8 +2,6 @@ package daemon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var binPath, _ = os.Executable() // store binary path
|
var binPath, _ = os.Executable() // store binary path
|
||||||
var socketPath = "/tmp/" + filepath.Base(binPath) + "-rcwd.sock" // store UNIX socket path
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var socketPath = "/tmp/" + filepath.Base(binPath) + "-rcwd.sock" // store UNIX socket path
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
var socketPath = `\\.\pipe\` + filepath.Base(binPath) + `-rcwd` // store Windows named pipe path
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// pidToPath returns the path of the executable that has the given PID.
|
// pidToPath returns the path of the executable that has the given PID.
|
||||||
|
// TODO remove reliance on system command OR verify authenticity of "lsof" binary
|
||||||
func pidToPath(pid int) string {
|
func pidToPath(pid int) string {
|
||||||
pidString := strconv.Itoa(pid)
|
pidString := strconv.Itoa(pid)
|
||||||
cmd := exec.Command("lsof", "-a", "-dtxt", "-p"+pidString)
|
cmd := exec.Command("lsof", "-a", "-dtxt", "-p"+pidString)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pidToPath returns the path of the executable that has the given PID.
|
||||||
|
func pidToPath(pid uint32) string {
|
||||||
|
// get a handle to the process
|
||||||
|
const PROCESS_QUERY_INFORMATION = 0x0400
|
||||||
|
const PROCESS_VM_READ = 0x0010
|
||||||
|
hProcess, err := windows.OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, pid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open process %d: %v", pid, err)
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(hProcess)
|
||||||
|
|
||||||
|
// query the process executable path
|
||||||
|
var pathBuf [syscall.MAX_PATH]uint16
|
||||||
|
pathLen := uint32(len(pathBuf))
|
||||||
|
windows.QueryFullProcessImageName(hProcess, 0, &pathBuf[0], &pathLen)
|
||||||
|
|
||||||
|
return syscall.UTF16ToString(pathBuf[:pathLen])
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/rpc"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
peercred "github.com/rwinkhart/peercred-mini"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var daemonHash []byte
|
var daemonHash []byte
|
||||||
@@ -20,49 +12,6 @@ var daemonHash []byte
|
|||||||
// RCWService provides an RPC method.
|
// RCWService provides an RPC method.
|
||||||
type RCWService struct{}
|
type RCWService struct{}
|
||||||
|
|
||||||
// Run should be called to start an RPC server.
|
|
||||||
func Run() {
|
|
||||||
// store the hash of the daemon binary
|
|
||||||
daemonHash = getFileHash(binPath)
|
|
||||||
|
|
||||||
// remove the socket file if it already exists
|
|
||||||
if _, err := os.Stat(socketPath); err == nil {
|
|
||||||
if err := os.Remove(socketPath); err != nil {
|
|
||||||
log.Fatalf("Failed to remove existing socket: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register RCWService with the RPC package
|
|
||||||
if err := rpc.Register(&RCWService{}); err != nil {
|
|
||||||
log.Fatalf("Error registering RPC service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// listen on the Unix domain socket
|
|
||||||
listener, err := net.Listen("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to listen on UNIX socket %s: %v", socketPath, err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
log.Printf("RPC daemon listening on unix://%s", socketPath)
|
|
||||||
|
|
||||||
// Accept connections (timeout after 3 minutes of inactivity)
|
|
||||||
for {
|
|
||||||
listener.(*net.UnixListener).SetDeadline(time.Now().Add(3 * time.Minute))
|
|
||||||
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if err.(net.Error).Timeout() {
|
|
||||||
log.Println("Three minutes have passed without any connections. Exiting...")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
log.Printf("Accept error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// use a goroutine to check the client's identity
|
|
||||||
go handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPass is the RPC method.
|
// GetPass is the RPC method.
|
||||||
// For now (as a test/example), it returns "hello" if the input is "hi".
|
// For now (as a test/example), it returns "hello" if the input is "hi".
|
||||||
func (h *RCWService) GetPass(request string, reply *string) error {
|
func (h *RCWService) GetPass(request string, reply *string) error {
|
||||||
@@ -73,29 +22,6 @@ func (h *RCWService) GetPass(request string, reply *string) error {
|
|||||||
return errors.New("unexpected input, expected \"hi\"")
|
return errors.New("unexpected input, expected \"hi\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleConn verifies the identity of the client.
|
|
||||||
// It uses the file descriptor of the connection to get the PID of the client,
|
|
||||||
// which is then used to get the path of the client's executable and calculate its hash.
|
|
||||||
// The passphrase is only returned if the client's executable hash matches the daemon's hash
|
|
||||||
// and if the request is coming from the same user.
|
|
||||||
// This ensures that only the binary the daemon is embedded in can retrieve the passphrase.
|
|
||||||
func handleConn(conn net.Conn) {
|
|
||||||
ucred := peercred.Get(conn)
|
|
||||||
|
|
||||||
// check if the RPC call is coming from an identical binary and from the same user
|
|
||||||
callingBinPath := pidToPath(ucred.PID)
|
|
||||||
if ucred.UID == strconv.Itoa(os.Getuid()) && bytes.Equal(getFileHash(callingBinPath), daemonHash) {
|
|
||||||
// valid client; hand off the connection to the RPC server
|
|
||||||
rpc.ServeConn(conn)
|
|
||||||
} else {
|
|
||||||
// invalid client; close the connection w/o a response,
|
|
||||||
// log the client's path, and kill the daemon
|
|
||||||
conn.Close()
|
|
||||||
log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) // TODO log to file
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFileHash returns the SHA256 hash of the file at the given path.
|
// getFileHash returns the SHA256 hash of the file at the given path.
|
||||||
func getFileHash(path string) []byte {
|
func getFileHash(path string) []byte {
|
||||||
file, _ := os.Open(path)
|
file, _ := os.Open(path)
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/rpc"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
peercred "github.com/rwinkhart/peercred-mini"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run should be called to start an RPC server.
|
||||||
|
func Run() {
|
||||||
|
// register RCWService with the RPC package
|
||||||
|
if err := rpc.Register(&RCWService{}); err != nil {
|
||||||
|
log.Fatalf("Error registering RPC service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the hash of the daemon binary
|
||||||
|
daemonHash = getFileHash(binPath)
|
||||||
|
|
||||||
|
// remove the socket file if it already exists
|
||||||
|
if _, err := os.Stat(socketPath); err == nil {
|
||||||
|
if err := os.Remove(socketPath); err != nil {
|
||||||
|
log.Fatalf("Failed to remove existing socket: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen on the Unix domain socket
|
||||||
|
listener, err := net.Listen("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to listen on UNIX socket %s: %v", socketPath, err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
log.Printf("RPC daemon listening on unix://%s", socketPath)
|
||||||
|
|
||||||
|
// accept connections (timeout after 3 minutes of inactivity)
|
||||||
|
for {
|
||||||
|
listener.(*net.UnixListener).SetDeadline(time.Now().Add(3 * time.Minute))
|
||||||
|
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if err.(net.Error).Timeout() {
|
||||||
|
log.Println("Three minutes have passed without any connections. Exiting...")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
log.Printf("Accept error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// use a goroutine to check the client's identity
|
||||||
|
go handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConn verifies the identity of the client.
|
||||||
|
// It uses the file descriptor of the connection to get the PID of the client,
|
||||||
|
// which is then used to get the path of the client's executable and calculate its hash.
|
||||||
|
// The passphrase is only returned if the client's executable hash matches the daemon's hash
|
||||||
|
// and if the request is coming from the same user.
|
||||||
|
// This ensures that only the binary the daemon is embedded in can retrieve the passphrase.
|
||||||
|
func handleConn(conn net.Conn) {
|
||||||
|
ucred := peercred.Get(conn)
|
||||||
|
|
||||||
|
// check if the RPC call is coming from an identical binary and from the same user
|
||||||
|
callingBinPath := pidToPath(ucred.PID)
|
||||||
|
if ucred.UID == strconv.Itoa(os.Getuid()) && bytes.Equal(getFileHash(callingBinPath), daemonHash) {
|
||||||
|
// valid client; hand off the connection to the RPC server
|
||||||
|
rpc.ServeConn(conn)
|
||||||
|
} else {
|
||||||
|
// invalid client; close the connection w/o a response,
|
||||||
|
// log the client's path, and kill the daemon
|
||||||
|
conn.Close()
|
||||||
|
log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) // TODO log to file
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/rpc"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call connects to the RPC server and requests the passphrase.
|
||||||
|
func Call() string {
|
||||||
|
// connect to the Windows named pipe
|
||||||
|
conn, err := winio.DialPipe(socketPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Dial error: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// create an RPC client using the connection
|
||||||
|
client := rpc.NewClient(conn)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// request the passphrase from the RPC server
|
||||||
|
var reply string
|
||||||
|
err = client.Call("RCWService.GetPass", "hi", &reply)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error calling RCWService.GetPass: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the passphrase
|
||||||
|
return reply
|
||||||
|
}
|
||||||
@@ -2,6 +2,13 @@ module rcw
|
|||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require github.com/rwinkhart/peercred-mini v0.0.0-20250405191724-d116525c2b41
|
require github.com/rwinkhart/peercred-mini v0.0.0-20250405214620-93ccab17290b
|
||||||
|
|
||||||
require github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405011819-d304d362d032 // indirect
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
|
golang.org/x/sys v0.32.0
|
||||||
|
)
|
||||||
|
|
||||||
|
replace golang.org/x/sys => github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405010723-99a5f0732c0e
|
||||||
|
|
||||||
|
replace github.com/rwinkhart/peercred-mini => ../peercred
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
github.com/rwinkhart/peercred-mini v0.0.0-20250405191724-d116525c2b41 h1:wUMbTEwPIf6bqLCAUmJ58uuYu6OX0iGa6S6NjYUXGPA=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/rwinkhart/peercred-mini v0.0.0-20250405191724-d116525c2b41/go.mod h1:cH3916WNofkve4MeeK56H0whuUUVf57iYht3nsUyEh8=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405011819-d304d362d032 h1:4qzc09ysQhkyI2oN7pLbZMQP+qHq2TlVRCkMSmbl8m8=
|
github.com/rwinkhart/peercred-mini v0.0.0-20250405214620-93ccab17290b h1:AEeo+zR+ZupGNvUwvu0YZcIX2X2sN1/S5Qw0nPyns3k=
|
||||||
github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405011819-d304d362d032/go.mod h1:U89aT1yaUAbs+9Dp9i1lZ2+5GsRNYk0l42+d1wykDBA=
|
github.com/rwinkhart/peercred-mini v0.0.0-20250405214620-93ccab17290b/go.mod h1:JNvJiNItSyk3JXfeDvdt1sPmY5NHqj7TJ2faTobtQHA=
|
||||||
|
github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405010723-99a5f0732c0e h1:YRpZcbGU/LVLwK87IURH4Sfye6Fv0ri1IRZ27loWpHs=
|
||||||
|
github.com/rwinkhart/sys-freebsd-13-xucred v0.0.0-20250405010723-99a5f0732c0e/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
|||||||
Reference in New Issue
Block a user