Last active
November 19, 2024 05:08
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice work