Files
rcw/daemon/2serverUNIXGeneric.go
T

106 lines
3.4 KiB
Go

//go:build !windows
package daemon
import (
"bytes"
"log"
"net"
"net/rpc"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/rwinkhart/go-boilerplate/security"
"github.com/rwinkhart/peercred-mini"
)
// Start is the entry point for the RPC server responsible for
// returning decrypted data to authenticated clients.
func Start(password []byte) {
// store password to be referenced by DecryptRequest method
globalPassword = password
// register RCWService with the RPC package
err := rpc.Register(&RCWService{})
if err != nil {
log.Fatalf("Error registering RPC service: %v", err)
}
// store the hash of the daemon binary
daemonHash, err = getFileHash(binPath)
if err != nil {
log.Fatalf("Error hashing daemon binary: %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)
// capture termination signals to ensure listener is closed
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
listener.Close()
security.ZeroizeBytes(globalPassword)
os.Exit(0)
}()
// accept connections until Timeout takes effect
for {
listener.(*net.UnixListener).SetDeadline(time.Now().Add(time.Duration(Timeout) * time.Second))
conn, err := listener.Accept()
if err != nil {
if err.(net.Error).Timeout() {
log.Println(strconv.Itoa(Timeout) + " seconds have passed without any connections. Exiting...")
listener.Close()
security.ZeroizeBytes(globalPassword)
os.Exit(0)
}
log.Printf("Accept error: %v", err)
continue
}
// use a goroutine to check the client's identity
go handleConn(conn, sigChan)
}
}
// 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 password 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 password.
func handleConn(conn net.Conn, sigChan chan os.Signal) {
ucred := peercred.Get(conn)
// check if the RPC call is coming from an identical binary and from the same user
callingBinPath := pidToPath(ucred.PID)
callingBinHash, err := getFileHash(callingBinPath)
if err != nil {
// calling binary hash failure
conn.Close()
log.Printf("Failed to hash calling binary: PID(%d), UID(%s), Path(%s) - %v", ucred.PID, ucred.UID, callingBinPath, err)
sigChan <- syscall.SIGTERM // this zeroizes globalPassword and triggers os.Exit(0)
return // explicitly return to avoid race
}
if ucred.UID == strconv.Itoa(os.Getuid()) && bytes.Equal(callingBinHash, 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)
sigChan <- syscall.SIGTERM // this zeroizes globalPassword and triggers os.Exit(0)
}
}