forked from solene/iblock
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.
This commit is contained in:
parent
5ac1e2631b
commit
92e8239a50
1
Makefile
1
Makefile
|
@ -16,6 +16,7 @@ clean:
|
||||||
|
|
||||||
install: iblock
|
install: iblock
|
||||||
install -o root -g wheel iblock ${PREFIX}/sbin/
|
install -o root -g wheel iblock ${PREFIX}/sbin/
|
||||||
|
install -o root -g wheel iblock.rc /etc/rc.d/iblock
|
||||||
|
|
||||||
test: clean iblock
|
test: clean iblock
|
||||||
@printf "hello\n" | nc -4 localhost 666
|
@printf "hello\n" | nc -4 localhost 666
|
||||||
|
|
41
README.md
41
README.md
|
@ -1,6 +1,6 @@
|
||||||
# iblock
|
# 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.
|
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
|
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
|
## Configure packet filter
|
||||||
|
|
||||||
Use this in `/etc/pf.conf`, choose which ports will trigger the ban from the variable:
|
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
|
# services triggering a block
|
||||||
blocking_tcp="{ 21 23 53 111 135 137:139 445 1433 25565 5432 3389 3306 27019 }"
|
blocking_tcp="{ 21 23 53 111 135 137:139 445 1433 25565 5432 3389 3306 27019 }"
|
||||||
|
|
||||||
table <blocked> persist
|
table <iblocked> persist
|
||||||
|
|
||||||
block in quick from <blocked> label iblock
|
block in quick from <iblocked> label iblock
|
||||||
pass in quick on egress inet proto tcp to port $blocking_tcp rdr-to 127.0.0.1 port 666
|
# iblock listens on port 2507
|
||||||
pass in quick on egress inet6 proto tcp to port $blocking_tcp rdr-to ::1 port 666
|
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`.
|
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
|
# 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).
|
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
|
# TODO
|
||||||
|
|
||||||
- A proper man page
|
- A proper man page
|
||||||
|
- a rc daemon
|
||||||
|
|
||||||
|
|
|
@ -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
|
161
main.c
161
main.c
|
@ -2,14 +2,18 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define PORT "2507"
|
||||||
|
#define BACKLOG 42
|
||||||
#define DEFAULT_TABLE "iblocked"
|
#define DEFAULT_TABLE "iblocked"
|
||||||
|
|
||||||
static void __dead
|
static void __dead
|
||||||
|
@ -19,72 +23,135 @@ usage(void)
|
||||||
exit(1);
|
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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct sockaddr_storage sock = {0};
|
char ip[INET6_ADDRSTRLEN] = {'\0'};
|
||||||
socklen_t slen = sizeof(sock);
|
const char *table = DEFAULT_TABLE;
|
||||||
char ip[INET6_ADDRSTRLEN] = {'\0'}; /* INET6_ADDRSTRLEN > INET_ADDRSTRLEN */
|
int sockfd = 0;
|
||||||
const char *table = DEFAULT_TABLE;
|
int new_fd = 0;
|
||||||
int ch, status = 0;
|
int retval = 0;
|
||||||
pid_t id;
|
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)
|
if (unveil("/usr/bin/doas", "rx") != 0)
|
||||||
err(1, "unveil");
|
err(1, "unveil");
|
||||||
if (pledge("exec inet proc stdio", NULL) != 0)
|
if (pledge("stdio inet exec proc rpath", NULL) != 0)
|
||||||
err(1, "pledge");
|
err(1, "pledge");
|
||||||
|
|
||||||
while ((ch = getopt(argc, argv, "")) != -1) {
|
if (argc > 2)
|
||||||
switch (ch) {
|
|
||||||
default:
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
|
|
||||||
if (argc > 1)
|
|
||||||
usage();
|
usage();
|
||||||
|
else if (argc == 2)
|
||||||
|
table = argv[1];
|
||||||
|
|
||||||
if (argc == 1)
|
/* initialize structures */
|
||||||
table = *argv;
|
memset(&client_addr, 0, sizeof(client_addr));
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
|
||||||
/* get socket structure */
|
/* set hints for socket */
|
||||||
if (getpeername(STDIN_FILENO, (struct sockaddr *)&sock, &slen))
|
hints.ai_family = AF_UNSPEC; /* ip4 or ip6 */
|
||||||
err(1, "getpeername");
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
/* get ip */
|
if ((retval = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
|
||||||
status = getnameinfo((struct sockaddr *)&sock, slen, ip, sizeof(ip),
|
syslog(LOG_DAEMON, "getaddrinfo failed");
|
||||||
NULL, 0, NI_NUMERICHOST);
|
err(1, "getaddrinfo :%s", gai_strerror(retval));
|
||||||
|
|
||||||
if (status != 0) {
|
|
||||||
syslog(LOG_DAEMON, "getnameinfo error: %s",
|
|
||||||
gai_strerror(status));
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (sock.ss_family) {
|
/* get a socket and bind */
|
||||||
case AF_INET: /* FALLTHROUGH */
|
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||||
case AF_INET6:
|
if ((sockfd = socket(p->ai_family,
|
||||||
id = fork();
|
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) {
|
if (id == -1) {
|
||||||
syslog(LOG_DAEMON, "fork error");
|
syslog(LOG_DAEMON, "fork error");
|
||||||
exit(1);
|
err(1, "fork");
|
||||||
} else if (id == 0) {
|
} else if (id == 0) { /* child process */
|
||||||
// child process
|
|
||||||
syslog(LOG_DAEMON, "blocking %s", ip);
|
syslog(LOG_DAEMON, "blocking %s", ip);
|
||||||
execl("/usr/bin/doas", "doas", "/sbin/pfctl",
|
runcmd(bancmd[0], bancmd);
|
||||||
"-t", table, "-T", "add", ip, NULL);
|
|
||||||
} else {
|
|
||||||
// parent process
|
|
||||||
wait(NULL);
|
|
||||||
syslog(LOG_DAEMON, "kill states for %s", ip);
|
syslog(LOG_DAEMON, "kill states for %s", ip);
|
||||||
execl("/usr/bin/doas", "doas", "/sbin/pfctl",
|
runcmd(killstatecmd[0], killstatecmd);
|
||||||
"-k", ip, NULL);
|
exit(0);
|
||||||
|
} else {
|
||||||
|
/* parent process */
|
||||||
|
waitpid(id, NULL, WNOHANG); /* non-blocking loop */
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
default:
|
|
||||||
exit(2);
|
|
||||||
}
|
}
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue