Skip to content

Instantly share code, notes, and snippets.

@atoonk
Last active November 19, 2024 05:08
Show Gist options
  • Save atoonk/8863c1cd561815ca1a0bc8cfe62cd2db to your computer and use it in GitHub Desktop.
Save atoonk/8863c1cd561815ca1a0bc8cfe62cd2db to your computer and use it in GitHub Desktop.
A simple Go TCP server demonstrating how to use libwrap with /etc/hosts.allow and /etc/hosts.deny for access control. The server listens on port 12344 and filters client connections based on TCP Wrappers rules, showcasing basic integration with hosts_access via cgo.
package main
/*
#cgo LDFLAGS: -lwrap
#include <stdlib.h>
#include <tcpd.h>
// Wrapper for request_init to avoid variadic arguments
// Initializes the request_info struct with daemon name and client address.
void my_request_init(struct request_info *req, const char *daemon, const char *client_addr) {
request_init(req, RQ_DAEMON, daemon, RQ_CLIENT_ADDR, client_addr, NULL);
}
// Wrapper for hosts_access to check access control
// Resolves client hostname and evaluates access rules in /etc/hosts.allow and /etc/hosts.deny.
int check_host_access(struct request_info *req) {
fromhost(req); // Resolve client hostname
return hosts_access(req); // Check access control
}
*/
import "C"
import (
"bufio"
"fmt"
"log"
"net"
"unsafe"
)
// daemonName specifies the name of the service/daemon, used in /etc/hosts.allow and /etc/hosts.deny.
const daemonName = "mydaemon"
func main() {
// Start a TCP server on port 12344
listener, err := net.Listen("tcp", ":12344")
if err != nil {
log.Fatalf("Failed to listen on port 12344: %v", err)
}
defer listener.Close()
fmt.Println("Server is listening on port 12344...")
for {
// Accept a new connection
conn, err := listener.Accept()
if err != nil {
log.Printf("Failed to accept connection: %v", err)
continue
}
// Extract the file descriptor from the connection
tcpConn := conn.(*net.TCPConn)
file, err := tcpConn.File()
if err != nil {
log.Printf("Failed to get file descriptor: %v", err)
conn.Close()
continue
}
fd := int(file.Fd())
// Get the client IP address
clientIP, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
// Check if the connection is allowed using TCP Wrappers
if !isAllowed(clientIP, fd) {
log.Printf("Connection from %s denied by libwrap", clientIP)
conn.Write([]byte("You are not welcome to use this service.\n"))
conn.Close()
file.Close() // Ensure the file descriptor is closed after rejection
continue
}
log.Printf("Connection from %s allowed by libwrap", clientIP)
// Handle the connection in a separate goroutine
go handleConnection(conn)
file.Close() // Close the file descriptor after starting the goroutine
}
}
// handleConnection processes an accepted connection by echoing back received data.
func handleConnection(conn net.Conn) {
defer conn.Close()
// Extract the source IP address from the connection
remoteAddr := conn.RemoteAddr().String()
sourceIP, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
log.Printf("Failed to parse remote address %s: %v", remoteAddr, err)
return
}
// Read and echo data from the client
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
data := scanner.Text()
fmt.Printf("Received from %s: %s\n", sourceIP, data)
fmt.Fprintf(conn, "%s\n", data) // Echo the data back to the client
}
if err := scanner.Err(); err != nil {
log.Printf("Error reading from connection: %v", err)
}
}
// isAllowed checks if a client connection is allowed by consulting TCP Wrappers rules.
func isAllowed(clientIP string, fd int) bool {
/*
example /etc/hosts.allow entry
mydaemon : 127.0.0.1 : allow
mydaemon : ALL : deny
*/
// Convert the daemon name and client IP to C strings
daemon := C.CString(daemonName)
defer C.free(unsafe.Pointer(daemon))
client := C.CString(clientIP)
defer C.free(unsafe.Pointer(client))
// Allocate memory for the request_info struct
req := (*C.struct_request_info)(C.malloc(C.sizeof_struct_request_info))
defer C.free(unsafe.Pointer(req)) // Free memory after use
// Initialize the request_info struct with daemon name and client IP
C.my_request_init(req, daemon, client)
// Set the file descriptor in the request_info struct
req.fd = C.int(fd)
// Check access control using hosts_access
allowed := C.check_host_access(req)
return allowed == 1
}
@verlaine-muhungu
Copy link

Nice work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment