From 92e8239a50f45c06a6254b3ebc2414616ebc9e4b Mon Sep 17 00:00:00 2001 From: prx Date: Sat, 8 Oct 2022 14:08:57 +0200 Subject: [PATCH] iblock is now its own server. inetd is no longer required. Some minor changes too: * doas is called with flag "-n", this avoid blocking iblock if doas ismisconfigured. * pfctl calls are run in separate fork process * iblock listening port is now the better 2507 instead of already used by other softwares 666. * Added a rc script to start iblock as daemon. --- Makefile | 1 + README.md | 41 +++++--------- iblock.rc | 10 ++++ main.c | 161 ++++++++++++++++++++++++++++++++++++++---------------- 4 files changed, 140 insertions(+), 73 deletions(-) create mode 100755 iblock.rc diff --git a/Makefile b/Makefile index 5756b59..a8874cc 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ clean: install: iblock install -o root -g wheel iblock ${PREFIX}/sbin/ + install -o root -g wheel iblock.rc /etc/rc.d/iblock test: clean iblock @printf "hello\n" | nc -4 localhost 666 diff --git a/README.md b/README.md index 8eb959a..80aee14 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # iblock -iblock is an inetd program adding the client IP to a Packet Filter table. +iblock is a program adding the client IP to a Packet Filter table. It is meant to be used to block scanner connecting on unused ports. @@ -22,26 +22,6 @@ Add in `/etc/doas.conf`: permit nopass _iblock cmd /sbin/pfctl ``` -## Configure inetd - -Start inetd service with this in `/etc/inetd.conf`: - -``` -666 stream tcp nowait _iblock /usr/local/sbin/iblock iblock -666 stream tcp6 nowait _iblock /usr/local/sbin/iblock iblock -``` - -You can change the PF table by adding it as a parameter like this: - -In this example, the parameter `blocklist` will add IPs to the `blocklist` PF table. - -``` -666 stream tcp nowait _iblock /usr/local/sbin/iblock iblock blocklist -666 stream tcp6 nowait _iblock /usr/local/sbin/iblock iblock blocklist -``` - -Default is "iblocked" table. - ## Configure packet filter Use this in `/etc/pf.conf`, choose which ports will trigger the ban from the variable: @@ -50,18 +30,25 @@ Use this in `/etc/pf.conf`, choose which ports will trigger the ban from the var # services triggering a block blocking_tcp="{ 21 23 53 111 135 137:139 445 1433 25565 5432 3389 3306 27019 }" -table persist +table persist -block in quick from label iblock -pass in quick on egress inet proto tcp to port $blocking_tcp rdr-to 127.0.0.1 port 666 -pass in quick on egress inet6 proto tcp to port $blocking_tcp rdr-to ::1 port 666 +block in quick from label iblock +# iblock listens on port 2507 +pass in quick on egress inet proto tcp to port $blocking_tcp rdr-to 127.0.0.1 port 2507 +pass in quick on egress inet6 proto tcp to port $blocking_tcp rdr-to ::1 port 2507 ``` Don't forget to reload the rules with `pfctl -f /etc/pf.conf`. +Use another table name by passing it as argument to iblock : + +``` +rcctl set iblock flags another_table_name +``` + # Get some statistics -Done! You can see IP banned using `pfctl -t blocked -T show` and iBlock will send blocked addresses to syslog. +Done! You can see IP banned using `pfctl -t iblocked -T show` and iblock will send blocked addresses to syslog. In the example I added a label to the block rule, you can use `pfctl -s labels` to view statistics from this rule, [see documentation for column meaning](https://man.openbsd.org/pfctl#s~8). @@ -69,3 +56,5 @@ In the example I added a label to the block rule, you can use `pfctl -s labels` # TODO - A proper man page +- a rc daemon + diff --git a/iblock.rc b/iblock.rc new file mode 100755 index 0000000..3cd6a67 --- /dev/null +++ b/iblock.rc @@ -0,0 +1,10 @@ +#!/bin/ksh + +daemon="/usr/local/sbin/iblock" +daemon_user="_iblock" + +. /etc/rc.d/rc.subr + +rc_bg=YES + +rc_cmd $1 diff --git a/main.c b/main.c index cafcebc..7f0f185 100644 --- a/main.c +++ b/main.c @@ -2,14 +2,18 @@ #include #include +#include #include #include #include #include +#include #include #include +#define PORT "2507" +#define BACKLOG 42 #define DEFAULT_TABLE "iblocked" static void __dead @@ -19,72 +23,135 @@ usage(void) exit(1); } +static void *get_in_addr(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) + return &(((struct sockaddr_in*)sa)->sin_addr); + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +static void runcmd(const char* cmd, const char** arg_list) +{ + + pid_t pid = fork(); + if (pid == -1) { + syslog(LOG_DAEMON, "fork error"); + err(1,"fork"); + } else if (pid == 0) { /* child */ + execv(cmd, (char **)arg_list); + /* if this is reached, then exec failed */ + syslog(LOG_DAEMON, "execv error"); + err(1,"execv"); + } else { /* parent */ + wait(NULL); + } +} + int main(int argc, char *argv[]) { - struct sockaddr_storage sock = {0}; - socklen_t slen = sizeof(sock); - char ip[INET6_ADDRSTRLEN] = {'\0'}; /* INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ - const char *table = DEFAULT_TABLE; - int ch, status = 0; - pid_t id; + char ip[INET6_ADDRSTRLEN] = {'\0'}; + const char *table = DEFAULT_TABLE; + int sockfd = 0; + int new_fd = 0; + int retval = 0; + socklen_t sin_size = 0; + struct addrinfo hints, *servinfo, *p; + struct sockaddr_storage client_addr; + const char *bancmd[] = { "/usr/bin/doas", "-n", + "/sbin/pfctl", "-t", table, + "-T", "add", ip, + NULL }; + const char *killstatecmd[] = { "/usr/bin/doas", "-n", + "/sbin/pfctl", + "-k", ip, + NULL }; + if (unveil("/usr/bin/doas", "rx") != 0) err(1, "unveil"); - if (pledge("exec inet proc stdio", NULL) != 0) + if (pledge("stdio inet exec proc rpath", NULL) != 0) err(1, "pledge"); - while ((ch = getopt(argc, argv, "")) != -1) { - switch (ch) { - default: - usage(); - } - } - argc -= optind; - argv += optind; - - if (argc > 1) + if (argc > 2) usage(); + else if (argc == 2) + table = argv[1]; - if (argc == 1) - table = *argv; + /* initialize structures */ + memset(&client_addr, 0, sizeof(client_addr)); + memset(&hints, 0, sizeof(hints)); - /* get socket structure */ - if (getpeername(STDIN_FILENO, (struct sockaddr *)&sock, &slen)) - err(1, "getpeername"); + /* set hints for socket */ + hints.ai_family = AF_UNSPEC; /* ip4 or ip6 */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; - /* get ip */ - status = getnameinfo((struct sockaddr *)&sock, slen, ip, sizeof(ip), - NULL, 0, NI_NUMERICHOST); - - if (status != 0) { - syslog(LOG_DAEMON, "getnameinfo error: %s", - gai_strerror(status)); - exit(1); + if ((retval = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { + syslog(LOG_DAEMON, "getaddrinfo failed"); + err(1, "getaddrinfo :%s", gai_strerror(retval)); } - switch (sock.ss_family) { - case AF_INET: /* FALLTHROUGH */ - case AF_INET6: - id = fork(); + /* get a socket and bind */ + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, + p->ai_socktype, + p->ai_protocol)) == -1) { + continue; + } + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + continue; + } + + break; + } + + freeaddrinfo(servinfo); + + if (p == NULL) { + syslog(LOG_DAEMON, "Failed to bind"); + err(1, "Failed to bind"); + } + + if (listen(sockfd, BACKLOG) == -1) { + syslog(LOG_DAEMON, "listen failed"); + err(1, "listen"); + } + + while (1) { + sin_size = sizeof(client_addr); + new_fd = accept(sockfd, + (struct sockaddr*)&client_addr, + &sin_size); + + if (new_fd == -1) + continue; + + /* get client ip */ + inet_ntop(client_addr.ss_family, + get_in_addr((struct sockaddr *)&client_addr), + ip, sizeof(ip)); + + close(new_fd); /* no longer needed */ + + pid_t id = fork(); if (id == -1) { syslog(LOG_DAEMON, "fork error"); - exit(1); - } else if (id == 0) { - // child process + err(1, "fork"); + } else if (id == 0) { /* child process */ syslog(LOG_DAEMON, "blocking %s", ip); - execl("/usr/bin/doas", "doas", "/sbin/pfctl", - "-t", table, "-T", "add", ip, NULL); - } else { - // parent process - wait(NULL); + runcmd(bancmd[0], bancmd); syslog(LOG_DAEMON, "kill states for %s", ip); - execl("/usr/bin/doas", "doas", "/sbin/pfctl", - "-k", ip, NULL); + runcmd(killstatecmd[0], killstatecmd); + exit(0); + } else { + /* parent process */ + waitpid(id, NULL, WNOHANG); /* non-blocking loop */ } - break; - default: - exit(2); } + close(sockfd); + return 0; }