/*
 * Souce: https://bani.com.br/2012/05/programmatically-managing-iptables-rules-in-c-iptc/
 *
 * changed by silvino at bk dot ru to meet personal taste
 * to query https://tldp.org/HOWTO/Querying-libiptc-HOWTO/mfunction.html
 */

#define BUFSIZ 64

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libiptc/libiptc.h>

struct {
    struct ipt_entry entry;
    struct xt_standard_target target;
} entry;

//struct xtc_handle *h;

static int insert_rule (
        //struct xtc_handle *h,
        const char *table,
        const char *chain,
        unsigned int src,
        int inverted_src,
        unsigned int dest,
        int inverted_dst,
        const char *target) {


    struct xtc_handle *h;
    int ret = 1;

    h = iptc_init (table);
    if (!h) {
        fprintf (stderr, "Could not init IPTC library: %s\n", iptc_strerror (errno));
        goto out;
    }

    entry.entry.ip.src.s_addr  = INADDR_ANY;
    entry.entry.ip.smsk.s_addr = 0;
    entry.entry.ip.dst.s_addr  = INADDR_ANY;
    entry.entry.ip.dmsk.s_addr = 0;

    /* target */
    entry.target.target.u.user.target_size = XT_ALIGN (sizeof (struct xt_standard_target));
    strncpy (entry.target.target.u.user.name, target, sizeof (entry.target.target.u.user.name));

    /* entry */
    entry.entry.target_offset = sizeof (struct ipt_entry);
    entry.entry.next_offset = entry.entry.target_offset + entry.target.target.u.user.target_size;

    if (src) {
        entry.entry.ip.src.s_addr  = src;
        entry.entry.ip.smsk.s_addr = 0xFFFFFFFF;
        if (inverted_src)
            entry.entry.ip.invflags |= IPT_INV_SRCIP;
    }

    if (dest) {
        entry.entry.ip.dst.s_addr  = dest;
        entry.entry.ip.dmsk.s_addr = 0xFFFFFFFF;
        if (inverted_dst)
            entry.entry.ip.invflags |= IPT_INV_DSTIP;
    }

    if (!iptc_append_entry (chain, (struct ipt_entry *) &entry, h)) {
        fprintf (stderr, "Could not insert a rule in iptables (table %s): %s\n", table, iptc_strerror (errno));
        return ret;
    }

    if (!iptc_commit (h)) {
        fprintf (stderr, "Could not commit changes in iptables (table %s): %s\n", table, iptc_strerror (errno));
        return ret;
    }

    ret = 0;
out:
    if (h)
        iptc_free(h);

    return ret;
}

void *xrealloc(void *ptr, size_t size) {
    ptr = realloc(ptr, size);
    if (ptr == NULL && size != 0) {
        exit(EXIT_FAILURE);
    }
    return ptr;
}

char *xstrdup(const char *s) {
    char *t;

    if (s == NULL)
        return NULL;

    t = strdup(s);

    if (t == NULL) {
        exit(EXIT_FAILURE);
    }

    return t;
}

char *file_read_line_alloc(FILE *fp) {
    char buf[BUFSIZ];
    unsigned int buf_len;
    char *line = NULL;
    unsigned int line_size = 0;
    int got_nl = 0;

    buf[0] = '\0';

    while (fgets(buf, BUFSIZ, fp)) {
        buf_len = strlen(buf);
        if (buf[buf_len - 1] == '\n') {
            buf_len--;
            buf[buf_len] = '\0';
            got_nl = 1;
        }
        if (line) {
            line_size += buf_len;
            line = xrealloc(line, line_size + 1);
            strncat(line, buf, line_size);
        } else {
            line_size = buf_len + 1;
            line = xstrdup(buf);
        }
        if (got_nl)
            break;
    }

    return line;
}


int main (int argc, char **argv) {
    const char *chain_in="blockip_in";
    const char *chain_out="blockip_out";
    long int total=0;

    if(argc > 1 && strcmp(argv[1], "-h") == 0){
        printf("\nip_blocker accepts a list of ip's from a file or stdin\n");
        printf("ip's are added to %s and %s chains\n\n", chain_in, chain_out);
        printf("ip_blocker [file]\n");
        exit(0);
    }

    unsigned int a, b, ret;
    char *line = NULL;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    if (!fp) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    memset (&entry, 0, sizeof (entry));

    while (line = file_read_line_alloc(fp)) {
        inet_pton (AF_INET, line, &a);
        ret = insert_rule (
            // handler
            //h,
            //const char *table,
            "filter",
            //const char *chain,
            chain_in,
            //unsigned int src,
            a,
            //int inverted_src,
            0,
            //unsigned int dest,
            NULL,
            //int inverted_dst,
            0,
            //const char *target
            "DROP");

        if(ret == 1){
            printf("Failed to add %s to %s does %s chain exists ?", line, chain_in, chain_in);
            free (line);
            fclose(fp);
            exit(EXIT_FAILURE);
        }

        ret = insert_rule (
            // handler
            //h,
            //const char *table,
            "filter",
            //const char *chain,
            chain_out,
            //unsigned int src,
            NULL,
            //int inverted_src,
            0,
            //unsigned int dest,
            a,
            //int inverted_dst,
            0,
            //const char *target
            "DROP");

        if(ret == 1){
            printf("Failed to add %s to %s does %s chain exists ?", line, chain_out, chain_out);
            free (line);
            fclose(fp);
            exit(EXIT_FAILURE);
        }
        total = total + 1;
        free (line);
        line = NULL;
    }

    if(fp){
        fclose(fp);
    }
    printf ("total ip's added; %i\n", total);
    return 0;
}