/***

fltedit.c	- the filter editing Facility

Copyright (c) Gerard Paul Java 1999, 2002

This software is open-source; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License in the included COPYING file for
details.

***/

#include <curses.h>
#include <panel.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <menurt.h>
#include <winops.h>
#include <labels.h>
#include <msgboxes.h>
#include "fltdefs.h"
#include "fltmgr.h"
#include "ipfilter.h"
#include "dirs.h"
#include "getpath.h"
#include "attrs.h"
#include "deskman.h"
#include "error.h"
#include "cidr.h"

extern int daemonized;

void init_filter_table(struct filterlist *fl)
{
    fl->head = fl->tail = NULL;
}

/*
 * Loads the filter from the filter file
 */

int loadfilter(char *filename, struct filterlist *fl, int resolve)
{
    struct filterent *fe;
    int pfd;
    unsigned int idx = 0;
    int br;
    int resolv_err = 0;
    char err_msg[80];

    init_filter_table(fl);

    pfd = open(filename, O_RDONLY);

    if (pfd < 0) {
        memset(err_msg, 0, 80);
        snprintf(err_msg, 80, "Error opening IP filter data file");
        write_error(err_msg, daemonized);
        fl->head = NULL;
        return 1;
    }
    do {
        fe = malloc(sizeof(struct filterent));
        br = read(pfd, &(fe->hp), sizeof(struct hostparams));

        if (br > 0) {
            fe->index = idx;
            if (resolve) {
                fe->saddr = nametoaddr(fe->hp.s_fqdn, &resolv_err);
                fe->daddr = nametoaddr(fe->hp.d_fqdn, &resolv_err);

                if (resolv_err) {
                    free(fe);
                    continue;
                }

                fe->smask = inet_addr(fe->hp.s_mask);
                fe->dmask = inet_addr(fe->hp.d_mask);
            }
            if (fl->head == NULL) {
                fl->head = fe;
                fe->prev_entry = NULL;
            } else {
                fl->tail->next_entry = fe;
                fe->prev_entry = fl->tail;
            }
            fe->next_entry = NULL;
            fl->tail = fe;
            idx++;
        } else {
            free(fe);
        }
    } while (br > 0);

    if (br == 0)
        close(pfd);

    return 0;
}

void savefilter(char *filename, struct filterlist *fl)
{
    struct filterent *fe = fl->head;
    int pfd;
    int bw;
    int resp;

    pfd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);

    while (fe != NULL) {
        bw = write(pfd, &(fe->hp), sizeof(struct hostparams));

        if (bw < 0) {
            tx_errbox("Unable to save filter changes", ANYKEY_MSG, &resp);
            clear_flt_tag();
            return;
        }
        fe = fe->next_entry;
    }

    close(pfd);
}

void print_hostparam_line(struct filterent *fe, int idx,
                          WINDOW * win, int attr)
{
    struct in_addr binmask;

    wattrset(win, attr);

    scrollok(win, 0);
    mvwprintw(win, idx, 0, "%78c", ' ');

    mvwaddnstr(win, idx, 1, fe->hp.s_fqdn, 20);
    if (inet_aton(fe->hp.s_mask, &binmask) == 0)
        inet_aton("255.255.255.255", &binmask);

    wprintw(win, "/%u", cidr_get_maskbits(binmask.s_addr));
    if (fe->hp.sport2 == 0)
        wprintw(win, ":%u", fe->hp.sport1);
    else
        wprintw(win, ":%u-%u", fe->hp.sport1, fe->hp.sport2);

    wmove(win, idx, 34);
    if (fe->hp.match_opposite != 'Y')
        wprintw(win, "-->");
    else
        wprintw(win, "<->");

    mvwaddnstr(win, idx, 38, fe->hp.d_fqdn, 15);

    if (inet_aton(fe->hp.d_mask, &binmask) == 0)
        inet_aton("255.255.255.255", &binmask);

    wprintw(win, "/%u", cidr_get_maskbits(binmask.s_addr));

    if (fe->hp.dport2 == 0)
        wprintw(win, ":%u", fe->hp.dport1);
    else
        wprintw(win, ":%u-%u", fe->hp.dport1, fe->hp.dport2);

    mvwprintw(win, idx, 76, "%c", toupper(fe->hp.reverse));
    wmove(win, idx, 0);
}

void update_hp_screen(struct filterlist *fl,
                      struct filterent *firstvisible, WINDOW * win)
{
    struct filterent *ftmp = firstvisible;
    int i;

    wattrset(win, STDATTR);
    if (firstvisible == NULL) {
        mvwprintw(win, 0, 0, "%78c", ' ');
        wmove(win, 0, 0);
        return;
    }

    scrollok(win, 0);
    for (i = 0; i <= 12; i++) {
        if (ftmp != NULL) {
            print_hostparam_line(ftmp, i, win, STDATTR);
            ftmp = ftmp->next_entry;
        } else {
            mvwprintw(win, i, 0, "%78c", ' ');
            wmove(win, i, 0);
        }
    }
    scrollok(win, 1);
}

int new_hp_entry(struct filterent **ftemp)
{
    int resp;
    *ftemp = malloc(sizeof(struct filterent));
    if (*ftemp == NULL) {
        tx_errbox("No memory for new filter entry", ANYKEY_MSG, &resp);
        return 0;
    }

    memset(*ftemp, 0, sizeof(struct filterent));
    return 1;
}


void modify_host_parameters(struct filterlist *fl)
{
    WINDOW *bwin;
    PANEL *bpanel;
    WINDOW *win;
    PANEL *panel;
    struct filterent *fe;
    struct filterent *ftemp;

    struct filterent *firstvisible = NULL;

    unsigned int idx = 0;
    int endloop_local = 0;
    int ch;
    int gh_aborted = 0;

    char s_portstr1[8];
    char d_portstr1[8];
    char s_portstr2[8];
    char d_portstr2[8];

    char inexstr[2];
    char matchop[2];

    bwin = newwin(15, 80, (LINES - 15) / 2, (COLS - 80) / 2);

    bpanel = new_panel(bwin);
    win = newwin(13, 78, (LINES - 13) / 2, (COLS - 78) / 2);
    panel = new_panel(win);

    wattrset(bwin, BOXATTR);
    tx_box(bwin, ACS_VLINE, ACS_HLINE);

    mvwprintw(bwin, 0, 2, " Source ");
    mvwprintw(bwin, 0, 38, " Destination ");
    mvwprintw(bwin, 0, 74, " I/E ");

    mvwprintw(bwin, 14, 1, " Filter Data ");
    tx_stdwinset(win);
    scrollok(win, 0);
    wattrset(win, STDATTR);
    tx_colorwin(win);

    move(LINES - 1, 1);
    tx_printkeyhelp("Up/Down", "-move ptr ", stdscr, HIGHATTR,
                    STATUSBARATTR);
    tx_printkeyhelp("I", "-insert ", stdscr, HIGHATTR, STATUSBARATTR);
    tx_printkeyhelp("A", "-add to list ", stdscr, HIGHATTR, STATUSBARATTR);
    tx_printkeyhelp("D", "-delete ", stdscr, HIGHATTR, STATUSBARATTR);
    tx_printkeyhelp("Enter", "-edit ", stdscr, HIGHATTR, STATUSBARATTR);
    tx_printkeyhelp("X/Ctrl+X", "-exit", stdscr, HIGHATTR, STATUSBARATTR);

    update_panels();
    doupdate();

    firstvisible = fl->head;

    update_hp_screen(fl, firstvisible, win);

    idx = 0;
    fe = firstvisible;

    update_panels();
    doupdate();

    do {
        if (fe != NULL) {
            print_hostparam_line(fe, idx, win, BARSTDATTR);
        }

        ch = wgetch(win);

        if (fe != NULL)
            print_hostparam_line(fe, idx, win, STDATTR);

        switch (ch) {
        case KEY_UP:
            if (fl->head != NULL) {
                if (fe->prev_entry != NULL) {
                    if (idx > 0)
                        idx--;
                    else {
                        scrollok(win, 1);
                        wscrl(win, -1);
                        firstvisible = firstvisible->prev_entry;
                    }
                    fe = fe->prev_entry;
                }
            }
            break;
        case KEY_DOWN:
            if (fl->head != NULL) {
                if (fe->next_entry != NULL) {
                    if (idx < 12)
                        idx++;
                    else {
                        scrollok(win, 1);
                        wscrl(win, 1);
                        firstvisible = firstvisible->next_entry;
                    }
                    fe = fe->next_entry;
                }
            }
            break;
        case 'i':
        case 'I':
        case KEY_IC:
            if (!new_hp_entry(&ftemp))
                break;

            gethostparams(&(ftemp->hp), "", "", "", "",
                          "", "", "", "", "I", "N", &gh_aborted);

            if (gh_aborted) {
                free(ftemp);
                continue;
            }

            if (fl->head == NULL) {
                ftemp->next_entry = ftemp->prev_entry = NULL;
                fl->head = fl->tail = ftemp;
                firstvisible = fl->head;
                idx = 0;
            } else {
                ftemp->next_entry = fe;
                ftemp->prev_entry = fe->prev_entry;

                /*
                 * Point firstvisible at new entry if we inserted at the
                 * top of the list.
                 */

                if (ftemp->prev_entry == NULL) {
                    fl->head = ftemp;
                    firstvisible = ftemp;
                } else
                    fe->prev_entry->next_entry = ftemp;

                fe->prev_entry = ftemp;
            }

            if (ftemp->next_entry == NULL)
                fl->tail = ftemp;

            fe = ftemp;
            update_hp_screen(fl, firstvisible, win);
            break;
        case 'a':
        case 'A':
        case 1:
            if (!new_hp_entry(&ftemp))
                break;

            gethostparams(&(ftemp->hp), "", "", "", "",
                          "", "", "", "", "I", "N", &gh_aborted);

            if (gh_aborted) {
                free(ftemp);
                continue;
            }

            /*
             * Add new node to the end of the list (or to the head if the
             * list is empty.
             */
            if (fl->tail != NULL) {
                fl->tail->next_entry = ftemp;
                ftemp->prev_entry = fl->tail;
            } else {
                fl->head = ftemp;
                fl->tail = ftemp;
                ftemp->prev_entry = ftemp->next_entry = NULL;
                firstvisible = fl->head;
                fe = ftemp;
                idx = 0;
            }

            ftemp->next_entry = NULL;
            fl->tail = ftemp;
            update_hp_screen(fl, firstvisible, win);
            break;
        case 'd':
        case 'D':
        case KEY_DC:
            if (fl->head != NULL) {
                /*
                 * Move firstvisible down if it's pointing to the target
                 * entry.
                 */

                if (firstvisible == fe)
                    firstvisible = fe->next_entry;

                /*
                 * Detach target node from list.
                 */
                if (fe->next_entry != NULL)
                    fe->next_entry->prev_entry = fe->prev_entry;
                else
                    fl->tail = fe->prev_entry;

                if (fe->prev_entry != NULL)
                    fe->prev_entry->next_entry = fe->next_entry;
                else
                    fl->head = fe->next_entry;

                /*
                 * Move pointer up if we're deleting the last entry.
                 * The list tail pointer has since been moved to the
                 * previous entry.
                 */
                if (fe->prev_entry == fl->tail) {
                    ftemp = fe->prev_entry;

                    /*
                     * Move screen pointer up. Really adjust the index if
                     * the pointer is anywhere below the top of the screen.
                     */
                    if (idx > 0)
                        idx--;
                    else {
                        /*
                         * Otherwise scroll the list down, and adjust the
                         * firstvisible pointer to point to the entry
                         * previous to the target.
                         */
                        if (ftemp != NULL) {
                            firstvisible = ftemp;
                        }
                    }
                } else
                    /*
                     * If we reach this point, we're deleting from before
                     * the tail of the list.  In that case, we point the
                     * screen pointer at the entry following the target.
                     */
                    ftemp = fe->next_entry;

                free(fe);
                fe = ftemp;
                update_hp_screen(fl, firstvisible, win);
            }
            break;
        case 13:
            if (fe != NULL) {
                sprintf(s_portstr1, "%u", fe->hp.sport1);
                sprintf(s_portstr2, "%u", fe->hp.sport2);
                sprintf(d_portstr1, "%u", fe->hp.dport1);
                sprintf(d_portstr2, "%u", fe->hp.dport2);
                inexstr[0] = toupper(fe->hp.reverse);
                inexstr[1] = '\0';
                matchop[0] = toupper(fe->hp.match_opposite);
                matchop[1] = '\0';

                gethostparams(&(fe->hp), fe->hp.s_fqdn, fe->hp.s_mask,
                              s_portstr1, s_portstr2, fe->hp.d_fqdn,
                              fe->hp.d_mask, d_portstr1, d_portstr2,
                              inexstr, matchop, &gh_aborted);

                update_hp_screen(fl, firstvisible, win);
            }

            break;
        case 'x':
        case 'X':
        case 'q':
        case 'Q':
        case 27:
        case 24:
            endloop_local = 1;
            break;
        case 'l':
        case 'L':
            tx_refresh_screen();
            break;
        }
        update_panels();
        doupdate();
    } while (!endloop_local);

    del_panel(panel);
    delwin(win);
    del_panel(bpanel);
    delwin(bwin);
    update_panels();
    doupdate();
}

/*
 * Remove a currently applied filter from memory
 */

void destroyfilter(struct filterlist *fl)
{
    struct filterent *fe;
    struct filterent *cfe;

    if (fl->head != NULL) {
        fe = fl->head;
        cfe = fl->head->next_entry;

        do {
            free(fe);
            fe = cfe;
            if (cfe != NULL)
                cfe = cfe->next_entry;
        } while (fe != NULL);

        fl->head = fl->tail = NULL;
    }
}


void definefilter(int *aborted)
{
    struct filterfileent ffile;
    char fntemp[14];
    struct filterlist fl;

    int pfd;
    int bw;
    int resp;

    /*
     * Lock facility
     */

    if (!mark_filter_change())
        return;

    get_filter_description(ffile.desc, aborted, "");

    if (*aborted) {
        clear_flt_tag();
        return;
    }
    genname(time((time_t *) NULL), fntemp);

    pfd =
        open(get_path(T_WORKDIR, fntemp), O_CREAT | O_WRONLY | O_TRUNC,
             S_IRUSR | S_IWUSR);
    if (pfd < 0) {
        tx_errbox("Cannot create filter data file", ANYKEY_MSG, &resp);
        *aborted = 1;
        clear_flt_tag();
        return;
    }

    close(pfd);

    pfd =
        open(OTHIPFLNAME, O_CREAT | O_WRONLY | O_APPEND,
             S_IRUSR | S_IWUSR);

    if (pfd < 0) {
        listfileerr(1);
        clear_flt_tag();
        return;
    }
    strcpy(ffile.filename, fntemp);
    bw = write(pfd, &ffile, sizeof(struct filterfileent));
    if (bw < 0)
        listfileerr(2);

    close(pfd);

    init_filter_table(&fl);
    modify_host_parameters(&fl);
    savefilter(get_path(T_WORKDIR, fntemp), &fl);
    destroyfilter(&fl);

    clear_flt_tag();
}

/*
 * Edit an existing filter
 */
void editfilter(int *aborted)
{
    char filename[FLT_FILENAME_MAX];
    struct filterlist fl;
    struct ffnode *flist;
    struct ffnode *ffile;
    struct filterfileent *ffe;

    if (!mark_filter_change())
        return;

    if (loadfilterlist(&flist) == 1) {
        listfileerr(1);
        destroyfilterlist(flist);
        clear_flt_tag();
        return;
    }
    pickafilter(flist, &ffile, aborted);

    clear_flt_tag();
    if ((*aborted)) {
        destroyfilterlist(flist);
        clear_flt_tag();
        return;
    }
    ffe = &(ffile->ffe);

    get_filter_description(ffe->desc, aborted, ffe->desc);

    if (*aborted) {
        destroyfilterlist(flist);
        clear_flt_tag();
        return;
    }
    strncpy(filename, get_path(T_WORKDIR, ffe->filename),
            FLT_FILENAME_MAX - 1);

    if (loadfilter(filename, &fl, FLT_DONTRESOLVE))
        return;

    modify_host_parameters(&fl);

    save_filterlist(flist);     /* This also destroys it */
    savefilter(filename, &fl);
    destroyfilter(&fl);
}

/*
 * Delete a filter record from the disk
 */

void delfilter(int *aborted)
{
    struct ffnode *fltfile;
    struct ffnode *fltlist;

    if (!mark_filter_change())
        return;

    if (loadfilterlist(&fltlist) == 1) {
        *aborted = 1;
        listfileerr(1);
        destroyfilterlist(fltlist);
        clear_flt_tag();
        return;
    }
    pickafilter(fltlist, &fltfile, aborted);

    if (*aborted) {
        clear_flt_tag();
        return;
    }
    unlink(get_path(T_WORKDIR, fltfile->ffe.filename));

    if (fltfile->prev_entry == NULL) {
        fltlist = fltlist->next_entry;
        if (fltlist != NULL)
            fltlist->prev_entry = NULL;
    } else {
        fltfile->prev_entry->next_entry = fltfile->next_entry;

        if (fltfile->next_entry != NULL)
            fltfile->next_entry->prev_entry = fltfile->prev_entry;
    }

    free(fltfile);

    save_filterlist(fltlist);
    clear_flt_tag();
    *aborted = 0;
}
