Skip to content

Instantly share code, notes, and snippets.

@grepwood
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grepwood/b65cd1a9869a161d6c23 to your computer and use it in GitHub Desktop.
Save grepwood/b65cd1a9869a161d6c23 to your computer and use it in GitHub Desktop.
Automatically unban IPs and force IP unbanning for APF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/* If you're using Windows, OSX, or OpenVZ, it's recommended you get grepline
* You can find it here https://github.com/grepwood/grepline */
/* Compile with GNU getline:
* cc count_ips.c -Wall -Wextra -pedantic -O2 -mtune=generic -DGNU_GETLINE -o count_ips
*
* Compile with grepline:
* cc grepline.c count_ips.c -Wall -Wextra -pedantic -O2 -mtune=generic -o count_ips */
#ifndef GNU_GETLINE
# include "grepline.h"
#else
# define grepline getline
#endif /* GNU_GETLINE */
/* If non-zero, file with given path exists */
int file_exist(const char *filename) {
struct stat buffer;
return (!stat(filename, &buffer));
}
/* This needs to be signed because "char" on its own
* defaults to "unsigned char" on PowerPC. On x86 it
* defaults to "signed char". This difference may cause
* trivial compile warnings. */
signed char IsInterProtFourAddr(char * arg) {
signed char result = 1;
size_t len = strlen(arg);
unsigned char counter;
/* GCC 4.4 and older will moan and complain if
* you declare these counters as smaller types than
* the types for which they serve and indices */
unsigned long dots;
unsigned long displacement[3];
unsigned long octet[4];
/* We have to account for a newline character */
if(len < 8 || len > 16) {
result = -1;
return result;
}
/* If the line is a comment */
if(arg[0] == '#') {
result = -2;
return result;
}
/* Valid IPv4 has 3 dots */
for(counter = 0, dots = 0; counter < len; ++counter) {
if(arg[counter] == '.') {
displacement[dots] = counter+1;
++dots;
}
}
if(dots != 3) {
result = -4;
return result;
}
octet[0] = strtoul(arg,NULL,10);
octet[1] = strtoul(arg+displacement[0],NULL,10);
octet[2] = strtoul(arg+displacement[1],NULL,10);
octet[3] = strtoul(arg+displacement[2],NULL,10);
/* We all watched that episode of CSI with
* IPv4 octets way over 300, right? */
for(counter = 0; counter < 4; ++counter) {
if(octet[counter] > 255) {
result = -8;
return result;
}
}
/* Let's make the IP alright to puts() */
arg[len-1] = 0;
return result;
}
void usage(int exitcode) {
puts("Copyright 2014 Michael Dec <grepwood@sucs.org>");
puts("This program reads a text file line by line,");
puts("counting IPv4 addresses inside of it.");
puts("It is especially geared towards dealing with");
puts("/etc/apf/deny_hosts.rules\n");
puts("-h this message");
puts("-f file path");
exit(exitcode);
}
int main(int argc, char * argv[]) {
int result = 0;
FILE * denyhosts_file = NULL;
char * line = NULL;
char * denyhosts_path = NULL;
unsigned long IP_Count = 0;
size_t wom;
char satisfied = 0;
int c;
while((c = getopt(argc, argv, "hf:")) != -1) {
switch(c) {
case 'h':
usage(-1);
break;
case 'f':
denyhosts_path = optarg;
satisfied += 1;
break;
default:
fprintf(stderr, "Unrecognised argument -%c\n", c);
usage(-2);
}
}
if (satisfied != 1) {
fputs("Insufficient parameters\n",stderr);
result = -4;
return result;
}
if(!file_exist(denyhosts_path)) {
result = -8;
fprintf(stderr, "%s does not exist\n", denyhosts_path);
return result;
}
denyhosts_file = fopen(denyhosts_path,"r");
while(!feof(denyhosts_file)) {
grepline(&line,&wom,denyhosts_file);
if(IsInterProtFourAddr(line) == 1) {
++IP_Count;
puts(line);
}
}
fclose(denyhosts_file);
free(line);
printf("IPv4 addresses: %lu\n",IP_Count);
return result;
}
#!/bin/bash
APF_UNBANS="/root/firewall_unbans"
IPS_TO_FORGIVE=`ls -C1 $APF_UNBANS | wc -l`
COUNT_IP_EXE="/usr/local/bin/count_ips"
APF_BAN_LIST="/etc/apf/deny_hosts.rules"
COUNT_IP_EXE_URL=""
UNBAN_SCHEDULER="schedule_unbans.sh"
echo "Before you deploy this, you must either compile count_ips.c,"
echo "move it to $COUNT_IP_EXE,"
echo "or define COUNT_IP_EXE_URL with a valid URL to the executable."
echo "You also have to redefine UNBAN_SCHEDULER if you renamed the"
echo "script that schedules unbans."
echo "After you've done that, feel free to wipe these echos and the following exit"
exit
if [ ! -f "$COUNT_IP_EXE" ]; then
printf "Downloading IP parser... "
curl $COUNT_IP_EXE_URL > $COUNT_IP_EXE 2>/dev/null
echo "done!"
fi
if [ ! -x "$COUNT_IP_EXE" ]; then
printf "Granting exe rights to IP parser... "
chmod +x $COUNT_IP_EXE
echo "done!"
fi
printf "Unbanning all IPs from $APF_UNBANS... "
for((COUNTER=1; COUNTER <= $IPS_TO_FORGIVE; COUNTER++)); do
CURRENT_IP=`ls -C1 $APF_UNBANS | head -n$COUNTER | tail -n1`
apf -u $CURRENT_IP 2>/dev/null 1>/dev/null
rm $APF_UNBANS/$CURRENT_IP
done
echo "done!"
echo "Unbanned IPs: $IPS_TO_FORGIVE"
COUNTER=`ps aux | grep $UNBAN_SCHEDULER | grep -v grep | wc -l`
if [ "$COUNTER" -gt "0" ]; then
printf "Killing all sleeping IP unbans... "
kill -9 `ps aux | grep $UNBAN_SCHEDULER | grep -v grep | awk '{print $2}'`
echo "done!"
fi
echo "Ban status:"
$COUNT_IP_EXE -f $APF_BAN_LIST
#!/bin/bash
COUNT_IP_EXE="/usr/local/bin/count_ips"
JOB_PID=$$
FIREWALL_UNBANS="/root/firewall_unbans"
APF_BAN_LIST="/etc/apf/deny_hosts.rules"
COUNT_IP_EXE_URL=""
DELAY="7 days"
echo "Before you deploy this, you must either compile count_ips.c,"
echo "move it to $COUNT_IP_EXE,"
echo "or define COUNT_IP_EXE_URL with a valid URL to the executable."
echo "You may also want to redefine DELAY for your own purposes."
echo "After you've done that, feel free to wipe these echos and the following exit"
exit
if [ ! -f "$COUNT_IP_EXE" ]; then
printf "Downloading IP parser... "
curl $COUNT_IP_EXE_URL > $COUNT_IP_EXE 2>/dev/null
echo "done!"
fi
if [ ! -x "$COUNT_IP_EXE" ]; then
printf "Granting exe rights to IP parser... "
chmod +x $COUNT_IP_EXE
echo "done!"
fi
if [ ! -d "$FIREWALL_UNBANS" ]; then
printf "Creating pending unban directory... "
mkdir -p $FIREWALL_UNBANS
echo "done!"
fi
printf "Copying and counting IPs... "
$COUNT_IP_EXE -f $APF_BAN_LIST 2>cip.$JOB_PID 1>cip.$JOB_PID
if grep -q ^"$APF_BAN_LIST does not exist"$ cip.$JOB_PID ; then
echo "oops"
echo "APF is not installed."
rm -f cip.$JOB_PID
exit
fi
echo "done!"
if [ "`tail -n1 cip.$JOB_PID`" == "IPv4 addresses: 0" ]; then
echo "The block list is empty."
rm -f cip.$JOB_PID
exit
fi
AMOUNT_OF_IPS=`tail -n1 cip.$JOB_PID | awk '{print $3}'`
for((COUNTER=1, TODO_COUNTER=0, ON_THEIR_WAY=0; COUNTER <= $AMOUNT_OF_IPS; COUNTER++)); do
CURRENT_IP=`head -n$COUNTER cip.$JOB_PID | tail -n1`
JOB_EXISTS=`ls $FIREWALL_UNBANS | grep $CURRENT_IP | wc -l`
if [ "$JOB_EXISTS" -eq "0" ]; then
TIME_OF_IP=`grep "{trust}\ deny\ all\ to\/from\ $CURRENT_IP" /var/log/apf_log | tail -n1 | awk '{print $1" "$2" "$3}'`
TIME_OF_IP=`date -d "$TIME_OF_IP"`
EXPIRATION_DATE=`date -d "$TIME_OF_IP+$DELAY" +%s`
TIME_NOW=`date +%s`
TIME_TO_SLEEP=`expr $EXPIRATION_DATE - $TIME_NOW`
if [ "$TIME_TO_SLEEP" -lt "0" ]; then
apf -u $CURRENT_IP 2>/dev/null
else
touch $FIREWALL_UNBANS/$CURRENT_IP
echo "Unblocking $CURRENT_IP in the background, in $TIME_TO_SLEEP seconds"
(sleep $TIME_TO_SLEEP; apf -u $CURRENT_IP 2>/dev/null && rm -f $FIREWALL_UNBANS/$CURRENT_IP) > /dev/null &
fi
TODO_COUNTER=`expr $TODO_COUNTER + 1`
else
ON_THEIR_WAY=`expr $ON_THEIR_WAY + 1`
fi
done
rm -f cip.$JOB_PID
# Note that sometimes you will sometimes have a job for
# an IP that's not deleted yet.
echo "IPs processed: $AMOUNT_OF_IPS"
echo "New jobs dispatched: $TODO_COUNTER"
echo "IPs that already were on their way out: $ON_THEIR_WAY"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment