hss/hss.c

360 lines
15 KiB
C
Executable file

#!/usr/bin/tcc -run
/* hss: Hurd Socket Stat, GPLv3 licensed, originally written by MARKMENTAL
* Made possible thanks to Debian, TCC and GNU
* Code shortened with the assistance of LongCat-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <hurd.h>
#include <hurd/paths.h>
#include <hurd/lookup.h>
#include <hurd/process.h>
#include <hurd/ifsock.h>
#include <mach/mach.h>
#include <mach/mach_interface.h>
#define TIMEOUT_SEC 2
#define DEFAULT_PROC_LIMIT 3
#define MAX_ETH_IPS 10
#define MAX_DISPLAY_IPS 3
#define MAX_PORTS 256
#define CONFIG_PATH "~/.config/.hssrc"
#define MAX_SERVICE_NAME_LEN 20
#define MAX_RANGE_SIZE 20
static const int common_modern_ports[] = {
8080, 8443, 3000, 5000, 8000, 8888, 9000, 9090, 1337, 27017
};
#define NUM_MODERN_PORTS (sizeof(common_modern_ports) / sizeof(common_modern_ports[0]))
struct port_entry {
int port;
char service_name[64];
};
static int is_common_port(int port) {
if (port >= 1 && port <= 1023) return 1;
for (size_t i = 0; i < NUM_MODERN_PORTS; i++) {
if (common_modern_ports[i] == port) return 1;
}
return 0;
}
static int get_common_modern_ports(struct port_entry *entries, int max_entries) {
int count = 0;
for (size_t i = 0; i < NUM_MODERN_PORTS && count < max_entries; i++) {
entries[count].port = common_modern_ports[i];
snprintf(entries[count].service_name, sizeof(entries[count].service_name),
"%d", common_modern_ports[i]);
count++;
}
return count;
}
static int parse_etc_services(struct port_entry *entries, int max_entries) {
FILE *f = fopen("/etc/services", "r");
if (!f) return 0;
char line[256];
int count = 0;
while (fgets(line, sizeof(line), f) && count < max_entries) {
if (line[0] == '#' || line[0] == '\n') continue;
char name[64];
int port_start, port_end;
char proto[16];
if (sscanf(line, "%63s %d-%d/%15s", name, &port_start, &port_end, proto) == 4) {
if (strstr(proto, "6")) continue;
if (port_end - port_start + 1 > MAX_RANGE_SIZE) {
port_end = port_start + MAX_RANGE_SIZE - 1;
}
for (int p = port_start; p <= port_end && count < max_entries; p++) {
if (p > 0 && p < 65536 && is_common_port(p)) {
entries[count].port = p;
if (strlen(name) > MAX_SERVICE_NAME_LEN) {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN - 3);
entries[count].service_name[MAX_SERVICE_NAME_LEN - 3] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 2] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 1] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
} else {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN);
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
}
count++;
}
}
} else if (sscanf(line, "%63s %d/%15s", name, &port_start, proto) == 3) {
if (strstr(proto, "6")) continue;
if (port_start > 0 && port_start < 65536 && is_common_port(port_start)) {
entries[count].port = port_start;
if (strlen(name) > MAX_SERVICE_NAME_LEN) {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN - 3);
entries[count].service_name[MAX_SERVICE_NAME_LEN - 3] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 2] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN - 1] = '.';
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
} else {
strncpy(entries[count].service_name, name, MAX_SERVICE_NAME_LEN);
entries[count].service_name[MAX_SERVICE_NAME_LEN] = '\0';
}
count++;
}
}
}
fclose(f);
return count;
}
static int parse_config_file(const char *path, struct port_entry *entries, int max_entries) {
char expanded_path[256];
if (path[0] == '~') {
const char *home = getenv("HOME");
if (!home) return 0;
snprintf(expanded_path, sizeof(expanded_path), "%s%s", home, path + 1);
path = expanded_path;
}
FILE *f = fopen(path, "r");
if (!f) return 0;
char line[256];
int count = 0;
while (fgets(line, sizeof(line), f) && count < max_entries) {
if (line[0] == '#' || line[0] == '\n') continue;
int port = atoi(line);
if (port > 0 && port < 65536) {
entries[count].port = port;
snprintf(entries[count].service_name, sizeof(entries[count].service_name), "custom-%d", port);
count++;
}
}
fclose(f);
return count;
}
static int merge_port_lists(struct port_entry *etc_services, int num_etc,
struct port_entry *modern, int num_modern,
struct port_entry *custom, int num_custom,
struct port_entry *merged, int max_merged) {
int count = 0;
int seen[65536] = {0};
for (int i = 0; i < num_etc && count < max_merged; i++) {
if (!seen[etc_services[i].port]) {
merged[count++] = etc_services[i];
seen[etc_services[i].port] = 1;
}
}
for (int i = 0; i < num_modern && count < max_merged; i++) {
if (!seen[modern[i].port]) {
merged[count++] = modern[i];
seen[modern[i].port] = 1;
}
}
for (int i = 0; i < num_custom && count < max_merged; i++) {
if (!seen[custom[i].port]) {
merged[count++] = custom[i];
seen[custom[i].port] = 1;
}
}
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (merged[j].port < merged[i].port) {
struct port_entry tmp = merged[i];
merged[i] = merged[j];
merged[j] = tmp;
}
}
}
return count;
}
static int get_all_ethernet_ips(char ips[][INET_ADDRSTRLEN], int max_ips) {
struct ifaddrs *ifaddr, *ifa;
int count = 0;
if (getifaddrs(&ifaddr) == -1) return 0;
for (ifa = ifaddr; ifa != NULL && count < max_ips; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET) continue;
if (!(ifa->ifa_flags & IFF_UP) || (ifa->ifa_flags & IFF_LOOPBACK)) continue;
struct sockaddr_in *sin = (struct sockaddr_in *)ifa->ifa_addr;
inet_ntop(AF_INET, &sin->sin_addr, ips[count], INET_ADDRSTRLEN);
count++;
}
freeifaddrs(ifaddr);
return count;
}
static int try_connect(const char *host, int port) {
struct addrinfo hints, *res, *rp;
char port_str[16];
int sock = -1, status = 0;
snprintf(port_str, sizeof(port_str), "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(host, port_str, &hints, &res) != 0) return 0;
for (rp = res; rp != NULL; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock < 0) continue;
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
if (connect(sock, rp->ai_addr, rp->ai_addrlen) < 0 && errno == EINPROGRESS) {
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
tv.tv_sec = TIMEOUT_SEC;
tv.tv_usec = 0;
if (select(sock + 1, NULL, &fdset, NULL, &tv) > 0) {
int so_error = 0;
socklen_t len = sizeof(so_error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) status = 1;
}
} else if (connect(sock, rp->ai_addr, rp->ai_addrlen) == 0) {
status = 1;
}
close(sock);
if (status) break;
}
freeaddrinfo(res);
return status;
}
static int probe_port(const char *host, int port, char *local_addr, size_t addr_len,
char (*eth_ips)[INET_ADDRSTRLEN], int num_eth_ips) {
if (!try_connect(host, port)) return 0;
int is_local = (strcmp(host, "localhost") == 0 || strcmp(host, "127.0.0.1") == 0);
if (is_local && num_eth_ips > 0) {
char verified_ips[MAX_DISPLAY_IPS][INET_ADDRSTRLEN];
int verified_count = 0;
for (int i = 0; i < num_eth_ips && verified_count < MAX_DISPLAY_IPS; i++) {
if (try_connect(eth_ips[i], port)) {
strncpy(verified_ips[verified_count], eth_ips[i], INET_ADDRSTRLEN);
verified_count++;
}
}
if (verified_count > 0) {
char addr_buf[256] = "";
for (int i = 0; i < verified_count; i++) {
if (i > 0) strncat(addr_buf, ", ", sizeof(addr_buf) - strlen(addr_buf) - 1);
strncat(addr_buf, verified_ips[i], sizeof(addr_buf) - strlen(addr_buf) - 1);
strncat(addr_buf, ":", sizeof(addr_buf) - strlen(addr_buf) - 1);
char port_buf[16];
snprintf(port_buf, sizeof(port_buf), "%d", port);
strncat(addr_buf, port_buf, sizeof(addr_buf) - strlen(addr_buf) - 1);
}
if (num_eth_ips > MAX_DISPLAY_IPS) strncat(addr_buf, ", +more", sizeof(addr_buf) - strlen(addr_buf) - 1);
snprintf(local_addr, addr_len, "%s", addr_buf);
return 1;
}
}
snprintf(local_addr, addr_len, "%s:%d", host, port);
return 1;
}
static int is_process_match(int port, const char *args, const char *port_needle) {
if (port == 22 && strstr(args, "sshd")) return 1;
if ((port == 80 || port == 443) && (strstr(args, "httpd") || strstr(args, "apache") || strstr(args, "nginx") || strstr(args, "lighttpd"))) return 1;
if (port == 21 && (strstr(args, "ftp") || strstr(args, "vsftpd") || strstr(args, "proftpd"))) return 1;
if (port == 3306 && (strstr(args, "mysql") || strstr(args, "mariadb"))) return 1;
if (port == 5432 && strstr(args, "postgres")) return 1;
if (port == 6379 && strstr(args, "redis")) return 1;
if (strstr(args, "python") || strstr(args, "node") || strstr(args, "java") || strstr(args, "ruby")) {
if (strstr(args, port_needle)) return 1;
}
return 0;
}
static void find_pids_for_port_hurd(process_t proc_server, int port, char *out_status, size_t buf_len, int max_processes) {
pid_t *pids = NULL;
mach_msg_type_number_t num_pids = 0;
char process_buf[256] = "";
int match_count = 0;
char port_needle[16];
snprintf(port_needle, sizeof(port_needle), "%d", port);
if (proc_getallpids(proc_server, &pids, &num_pids) != 0) {
snprintf(out_status, buf_len, "users:((\"unknown\",pid=0))");
return;
}
for (size_t i = 0; i < num_pids; i++) {
pid_t current_pid = pids[i];
char *args = NULL;
mach_msg_type_number_t args_len = 0;
if (proc_getprocargs(proc_server, current_pid, &args, &args_len) == 0 && args != NULL && args_len > 0) {
for (size_t j = 0; j < args_len; j++) {
if (args[j] == '\0') args[j] = ' ';
}
if (is_process_match(port, args, port_needle)) {
if (max_processes > 0 && match_count >= max_processes) {
strncat(process_buf, ",+more", sizeof(process_buf) - strlen(process_buf) - 1);
match_count++;
vm_deallocate(mach_task_self(), (vm_address_t)args, args_len);
break;
}
if (match_count > 0) strncat(process_buf, ",", sizeof(process_buf) - strlen(process_buf) - 1);
char *basename = strrchr(args, '/');
basename = basename ? basename + 1 : args;
char *space = strchr(basename, ' ');
if (space) *space = '\0';
char proc_entry[64];
snprintf(proc_entry, sizeof(proc_entry), "(\"%s\",pid=%d)", basename, current_pid);
strncat(process_buf, proc_entry, sizeof(process_buf) - strlen(process_buf) - 1);
match_count++;
}
vm_deallocate(mach_task_self(), (vm_address_t)args, args_len);
}
}
if (pids && num_pids > 0) vm_deallocate(mach_task_self(), (vm_address_t)pids, num_pids * sizeof(pid_t));
if (match_count > 0) snprintf(out_status, buf_len, "users:(%s)", process_buf);
else snprintf(out_status, buf_len, "users:((\"unknown\",pid=0))");
}
int main(int argc, char *argv[]) {
const char *target = (argc > 1) ? argv[1] : "localhost";
int proc_limit = DEFAULT_PROC_LIMIT;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--all-processes") == 0) proc_limit = 0;
else if (strncmp(argv[i], "--process-limit=", 16) == 0) proc_limit = atoi(argv[i] + 16);
}
char status[256];
char local_addr[256];
char eth_ips[MAX_ETH_IPS][INET_ADDRSTRLEN];
int num_eth_ips = get_all_ethernet_ips(eth_ips, MAX_ETH_IPS);
process_t proc_server = getproc();
if (proc_server == MACH_PORT_NULL) {
fprintf(stderr, "hss: Error fetching Hurd proc server port via getproc()\n");
return 1;
}
file_t sock_port = file_name_lookup(_SERVERS_SOCKET "/2", 0, 0);
if (sock_port == MACH_PORT_NULL) fprintf(stderr, "hss: Warning - IPv4 socket translator not responding\n");
else mach_port_deallocate(mach_task_self(), sock_port);
struct port_entry etc_services[MAX_PORTS];
int num_etc = parse_etc_services(etc_services, MAX_PORTS);
struct port_entry modern[MAX_PORTS];
int num_modern = get_common_modern_ports(modern, MAX_PORTS);
struct port_entry custom[MAX_PORTS];
int num_custom = parse_config_file(CONFIG_PATH, custom, MAX_PORTS);
struct port_entry merged[MAX_PORTS];
int num_merged = merge_port_lists(etc_services, num_etc, modern, num_modern,
custom, num_custom, merged, MAX_PORTS);
printf("%-8s %-12s %-30s %s\n", "Netid", "State", "Local Address:Port", "Process");
for (int i = 0; i < num_merged; i++) {
int port = merged[i].port;
if (!probe_port(target, port, local_addr, sizeof(local_addr), eth_ips, num_eth_ips)) continue;
find_pids_for_port_hurd(proc_server, port, status, sizeof(status), proc_limit);
printf("%-8s %-12s %-30s %s\n", "tcp", "LISTEN", local_addr, status);
}
mach_port_deallocate(mach_task_self(), proc_server);
return 0;
}