#include "libiptc.h"

int x_get_rule(const char *chain, struct xtc_handle *handle, char *ipv4)
{
    int r = 1;
    const struct ipt_entry *entry;
    struct ipt_entry_match *entry_match;

    for (entry = iptc_first_rule(chain, handle); entry; entry = iptc_next_rule(entry, handle)) {
        const char *t = iptc_get_target(entry, handle);
        entry_match = (struct ipt_entry_match *)entry->elems;
        //printf("u.user.name: %s\n", entry_match->u.user.name);
        char addr[64];
        memset(addr, 0, 64);
        inet_ntop(AF_INET, &(entry->ip.src), addr, sizeof(addr));
        //printf("%s\n", addr);
        //printf("%s\n", t);
        if (0 == strcmp(ipv4, addr) && 0 == strcmp(t, "DROP") && 0 == strcmp(entry_match->u.user.name, "tcp")) {
            r = 0;
            break;
        }
    }

    return r;

}

int show_all_rule(char *ipv4)
{
    int r = 0;
    struct xtc_handle *handle;
    const char *chain = NULL;
    struct ipt_counters counter;

    handle = iptc_init("filter");

    for (chain = iptc_first_chain(handle); chain; chain = iptc_next_chain(handle)) {
        if (chain != NULL && handle != NULL) {
            iptc_get_policy(chain, &counter, handle);
        }

        r = x_get_rule(chain, handle, ipv4);
        if (r == 0) {
            break;
        }
    }
    
    iptc_free(handle);

    return r;
}

static u_int16_t parse_port(const char *port)
{
    return atoi(port);
}

static void parse_ports(const char *portstring, u_int16_t * ports)
{
    char *buffer;
    char *cp;

    buffer = strdup(portstring);
    if ((cp = strchr(buffer, ':')) == NULL)
        ports[0] = ports[1] = parse_port(buffer);
    else {
        *cp = '\0';
        cp++;

        ports[0] = buffer[0] ? parse_port(buffer) : 0;
        ports[1] = cp[0] ? parse_port(cp) : 0xFFFF;
    }
    
    free(buffer);
}

struct ipt_entry_match *get_match(const char *sports, const char *dports, unsigned int *nfcache, char *protocol)
{
    struct ipt_entry_match *match;
    struct ipt_udp *udpinfo;
    size_t size;

    size = IPT_ALIGN(sizeof(*match)) + IPT_ALIGN(sizeof(*udpinfo));
    match = (struct ipt_entry_match *)calloc(1, size);
    match->u.match_size = size;
    strncpy(match->u.user.name, protocol, IPT_FUNCTION_MAXNAMELEN - 2);

    udpinfo = (struct ipt_udp *)match->data;
    udpinfo->spts[1] = udpinfo->dpts[1] = 0xFFFF;
    //printf("sports=%s,dports=%s\n", sports, dports);
    if (sports) {
        *nfcache |= NFC_IP_SRC_PT;
        parse_ports(sports, udpinfo->spts);
    }
    if (dports) {
        *nfcache |= NFC_IP_DST_PT;
        parse_ports(dports, udpinfo->dpts);
    }

    return match;
}

int iptc_add_rule(const char *table, const char *chain, int protocol, const char *iniface, const char *outiface, unsigned int src, unsigned int dest, const char *srcports, const char *destports, const char *target, const char *dnat_to, const int append)
{
    struct xtc_handle *handle;
    struct ipt_entry *chain_entry;
    struct ipt_entry_match *entry_match = NULL;
    struct ipt_entry_target *entry_target = NULL;
    ipt_chainlabel labelit;
    long match_size;
    int result = 0;

    chain_entry = (struct ipt_entry *)calloc(1, sizeof(*chain_entry));

    if (src != 0) {
        chain_entry->ip.src.s_addr = src;
        chain_entry->ip.smsk.s_addr = 0xFFFFFFFF;
    }
    if (dest != 0) {
        chain_entry->ip.dst.s_addr = dest;
        chain_entry->ip.dmsk.s_addr = 0xFFFFFFFF;
    }

    if (iniface)
        strncpy(chain_entry->ip.iniface, iniface, IFNAMSIZ);
    if (outiface)
        strncpy(chain_entry->ip.outiface, outiface, IFNAMSIZ);

    chain_entry->ip.proto = protocol;
    if (IPPROTO_UDP == protocol)
        entry_match = get_match(srcports, destports, &chain_entry->nfcache, "udp");
    if (IPPROTO_TCP == protocol)
        entry_match = get_match(srcports, destports, &chain_entry->nfcache, "tcp");

    if (strcmp(target, "") == 0 || strcmp(target, IPTC_LABEL_ACCEPT) == 0 || strcmp(target, IPTC_LABEL_DROP) == 0 || strcmp(target, IPTC_LABEL_QUEUE) == 0 || strcmp(target, IPTC_LABEL_RETURN) == 0) {
        size_t size;
        size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(int));
        entry_target = (struct ipt_entry_target *)calloc(1, size);
        entry_target->u.user.target_size = size;
        strncpy(entry_target->u.user.name, target, XT_EXTENSION_MAXNAMELEN);
    }
    if (entry_match) {
        match_size = entry_match->u.match_size;
    } else
        match_size = 0;

    struct ipt_entry *tmp_ipt = chain_entry;
    chain_entry = (struct ipt_entry *)realloc(chain_entry, sizeof(*chain_entry) + match_size + entry_target->u.target_size);
    if (chain_entry == NULL) {
        free(tmp_ipt);
    }

    memcpy(chain_entry->elems + match_size, entry_target, entry_target->u.target_size);
    chain_entry->target_offset = sizeof(*chain_entry) + match_size;
    chain_entry->next_offset = sizeof(*chain_entry) + match_size + entry_target->u.target_size;
    if (entry_match) {
        memcpy(chain_entry->elems, entry_match, match_size);
    }

    handle = iptc_init(table);
    if (!handle) {
        printf("libiptc error: Can't initialize table %s, %s\n", table, iptc_strerror(errno));
        free(chain_entry);
        free(entry_target);
        if (entry_match)
            free(entry_match);
        return 1;
    }

    strncpy(labelit, chain, sizeof(ipt_chainlabel));
    //printf("Chain name:%s\n", chain);
    result = iptc_is_chain(chain, handle);
    if (!result) {
        printf("libiptc error: Chain %s does not exist!\n", chain);
        free(chain_entry);
        free(entry_target);
        if (entry_match)
            free(entry_match);
        return 1;
    }
    
    if (append)
    {
        result = iptc_append_entry(labelit, chain_entry, handle);
    }
    else
    {
        result = iptc_insert_entry(labelit, chain_entry, 0, handle);
    }
    
    if (!result) {
        printf("libiptc error: Can't add, %s\n", iptc_strerror(errno));
        free(chain_entry);
        free(entry_target);
        if (entry_match)
            free(entry_match);
        return 1;
    }
    
    result = iptc_commit(handle);
    if (!result) {
        printf("libiptc error: Commit error, %s\n", iptc_strerror(errno));
        free(chain_entry);
        free(entry_target);
        if (entry_match)
            free(entry_match);
        return 1;
    }

    if (entry_match)
        free(entry_match);
    free(entry_target);
    free(chain_entry);
    
    iptc_free(handle);

    return 0;
}
