/* Static network configurator module for netcfg.
 *
 * Licensed under the terms of the GNU General Public License
 */

#include "netcfg.h"
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <debian-installer.h>
#include <assert.h>

struct in_addr old_ipaddress = { 0 };
struct in_addr network = { 0 };
struct in_addr broadcast = { 0 };
struct in_addr netmask = { 0 };
struct in_addr pointopoint = { 0 };

int netcfg_get_ipaddress(struct debconfclient *client)
{
    int ret, ok = 0;

    old_ipaddress = ipaddress;

    while (!ok) {
        debconf_input (client, "critical", "netcfg/get_ipaddress");
        ret = debconf_go (client);

        if (ret)
            return ret;

        debconf_get(client, "netcfg/get_ipaddress");
        ok = inet_pton (AF_INET, client->value, &ipaddress);

        if (!ok) {
            debconf_capb(client);
            debconf_input (client, "critical", "netcfg/bad_ipaddress");
            debconf_capb(client, "backup");
            debconf_go (client);
        }
    }

    return 0;
}

int netcfg_get_pointopoint(struct debconfclient *client)
{
    int ret, ok = 0;

    while (!ok) {
        debconf_input(client, "critical", "netcfg/get_pointopoint");
        ret = debconf_go(client);

        if (ret)
            return ret;

        debconf_get(client, "netcfg/get_pointopoint");

        if (empty_str(client->value)) {           /* No P-P is ok */
            memset(&pointopoint, 0, sizeof(struct in_addr));
            return 0;
        }

        ok = inet_pton (AF_INET, client->value, &pointopoint);

        if (!ok) {
            debconf_capb(client);
            debconf_input (client, "critical", "netcfg/bad_ipaddress");
            debconf_go (client);
            debconf_capb(client, "backup");
        }
    }

    inet_pton (AF_INET, "255.255.255.255", &netmask);
    network = ipaddress;
    gateway = pointopoint;

    return 0;
}

int netcfg_get_netmask(struct debconfclient *client)
{
    int ret, ok = 0;
    char ptr1[INET_ADDRSTRLEN];
    struct in_addr old_netmask = netmask;

    while (!ok) {
        debconf_input (client, "critical", "netcfg/get_netmask");
        ret = debconf_go(client);

        if (ret)
            return ret;

        debconf_get (client, "netcfg/get_netmask");

        ok = inet_pton (AF_INET, client->value, &netmask);

        if (!ok) {
            debconf_capb(client);
            debconf_input (client, "critical", "netcfg/bad_ipaddress");
            debconf_go (client);
            debconf_capb(client, "backup");
        }
    }

    if (ipaddress.s_addr != old_ipaddress.s_addr ||
        netmask.s_addr != old_netmask.s_addr
        ) {
        network.s_addr = ipaddress.s_addr & netmask.s_addr;
        broadcast.s_addr = (network.s_addr | ~netmask.s_addr);

        /* Preseed gateway */
        gateway.s_addr = ipaddress.s_addr & netmask.s_addr;
        gateway.s_addr |= htonl(1);
    }

    inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1));

    /* if you entered a .1 ip, you'll get a .1 back, so makes sense
     * to clear the last bit */
    if (gateway.s_addr == ipaddress.s_addr) {
        char* ptr = strrchr(ptr1, '.');
        assert (ptr); /* if there's no dot in ptr1 we're in deep shit */
        ptr[1] = '\0';
    }

    debconf_get(client, "netcfg/get_gateway");
    if (empty_str(client->value))
        debconf_set(client, "netcfg/get_gateway", ptr1);

    return 0;
}

int netcfg_get_gateway(struct debconfclient *client)
{
    int ret, ok = 0;
    char *ptr;

    while (!ok) {
        debconf_input (client, "critical", "netcfg/get_gateway");
        ret = debconf_go(client);

        if (ret)
            return ret;

        debconf_get(client, "netcfg/get_gateway");
        ptr = client->value;

        if (empty_str(ptr) || /* No gateway, that's fine */
            (strcmp(ptr, "none") == 0)) /* special case for preseeding */ {
            /* clear existing gateway setting */
            memset(&gateway, 0, sizeof(struct in_addr));
            return 0;
        }

        ok = inet_pton (AF_INET, ptr, &gateway);

        if (!ok) {
            debconf_capb(client);
            debconf_input (client, "critical", "netcfg/bad_ipaddress");
            debconf_go (client);
            debconf_capb(client, "backup");
        }
    }

    return 0;
}

static int netcfg_write_static(char *domain, struct in_addr nameservers[])
{
    char ptr1[INET_ADDRSTRLEN];
    FILE *fp;

    if ((fp = file_open(NETWORKS_FILE, "w"))) {
        fprintf(fp, "default\t\t0.0.0.0\n");
        fprintf(fp, "loopback\t127.0.0.0\n");
        fprintf(fp, "link-local\t169.254.0.0\n");
        fprintf(fp, "localnet\t%s\n", inet_ntop (AF_INET, &network, ptr1, sizeof (ptr1)));
        fclose(fp);
    } else
        goto error;

    if ((fp = file_open(INTERFACES_FILE, "a"))) {
        fprintf(fp, "\n# The primary network interface\n");
        fprintf(fp, "auto %s\n", interface);
        fprintf(fp, "iface %s inet static\n", interface);
        fprintf(fp, "\taddress %s\n", inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)));
        fprintf(fp, "\tnetmask %s\n", inet_ntop (AF_INET, &netmask, ptr1, sizeof (ptr1)));
        fprintf(fp, "\tnetwork %s\n", inet_ntop (AF_INET, &network, ptr1, sizeof (ptr1)));
        fprintf(fp, "\tbroadcast %s\n", inet_ntop (AF_INET, &broadcast, ptr1, sizeof (ptr1)));
        if (gateway.s_addr)
            fprintf(fp, "\tgateway %s\n", inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1)));
        if (pointopoint.s_addr)
            fprintf(fp, "\tpointopoint %s\n", inet_ntop (AF_INET, &pointopoint, ptr1, sizeof (ptr1)));
        /*
         * Write wireless-tools options
         */
        if (is_wireless_iface(interface)) {
            fprintf(fp, "\t# wireless-* options are implemented by the wireless-tools package\n");
            fprintf(fp, "\twireless-mode %s\n",
                    (mode == MANAGED) ? "managed" : "ad-hoc");
            fprintf(fp, "\twireless-essid %s\n",
                    (essid && *essid) ? essid : "any");

            if (wepkey != NULL)
                fprintf(fp, "\twireless-key1 %s\n", wepkey);
        }
        /*
         * Write resolvconf options
         *
         * This is useful for users who intend to install resolvconf
         * after the initial installation.
         *
         * This code should be kept in sync with the code that writes
         * this information to the /etc/resolv.conf file.  If netcfg
         * becomes capable of configuring multiple network interfaces
         * then the user should be asked for dns information on a
         * per-interface basis so that per-interface dns options
         * can be written here.
         */
        if (nameservers[0].s_addr || (domain && !empty_str(domain))) {
            int i = 0;
            fprintf(fp, "\t# dns-* options are implemented by the resolvconf package, if installed\n");
            if (nameservers[0].s_addr) {
                fprintf(fp, "\tdns-nameservers");
                while (nameservers[i].s_addr)
                    fprintf(fp, " %s",
                            inet_ntop (AF_INET, &nameservers[i++], ptr1, sizeof (ptr1)));
                fprintf(fp, "\n");
            }
            if (domain && !empty_str(domain))
                fprintf(fp, "\tdns-search %s\n", domain);
        }
        fclose(fp);
    } else
        goto error;

    if (netcfg_write_resolv(domain, nameservers))
        goto error;

    return 0;
error:
    return -1;
}

int netcfg_write_resolv (char* domain, struct in_addr* nameservers)
{
    FILE* fp = NULL;
    char ptr1[INET_ADDRSTRLEN];

    if ((fp = file_open(RESOLV_FILE, "w"))) {
        int i = 0;
        if (domain && !empty_str(domain))
            fprintf(fp, "search %s\n", domain);

        while (nameservers[i].s_addr)
            fprintf(fp, "nameserver %s\n",
                    inet_ntop (AF_INET, &nameservers[i++], ptr1, sizeof (ptr1)));

        fclose(fp);
        return 0;
    }
    else
        return 1;
}

int netcfg_activate_static(struct debconfclient *client)
{
    int rv = 0, masksize;
    char buf[256];
    char ptr1[INET_ADDRSTRLEN];

#ifdef __GNU__
    snprintf(buf, sizeof(buf),
             "settrans -fgap /servers/socket/2 /hurd/pfinet --interface=%s --address=%s",
             interface, inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)));
    di_snprintfcat(buf, sizeof(buf), " --netmask=%s",
                   inet_ntop (AF_INET, &netmask, ptr1, sizeof (ptr1)));

    if (gateway.s_addr)
        di_snprintfcat(buf, sizeof(buf), " --gateway=%s",
                       inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1)));

    buf[sizeof(buf) - 1] = '\0';

    /* NB: unfortunately we cannot use di_exec_shell_log() here, as the active
     * translator would capture its pipe and make it hang forever. */
    rv |= di_exec_shell(buf);

#elif defined(__FreeBSD_kernel__)
    deconfigure_network();
    
    loop_setup();
    interface_up(interface);
    
    /* Flush all previous addresses, routes */
    snprintf(buf, sizeof(buf), "ifconfig %s inet 0 down", interface);
    rv |= di_exec_shell_log(buf);
    
    snprintf(buf, sizeof(buf), "ifconfig %s up", interface);
    rv |= di_exec_shell_log(buf);
    
    snprintf(buf, sizeof(buf), "ifconfig %s %s",
             interface,
             inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)));
    
    /* avoid using a second buffer */
    di_snprintfcat(buf, sizeof(buf), " netmask %s",
                   inet_ntop (AF_INET, &netmask, ptr1, sizeof (ptr1)));

    /* avoid using a third buffer */
    di_snprintfcat(buf, sizeof(buf), " broadcast %s",
                   inet_ntop (AF_INET, &broadcast, ptr1, sizeof (ptr1)));
    
    di_info("executing: %s", buf);
    rv |= di_exec_shell_log(buf);
    
    if (pointopoint.s_addr) {
        snprintf(buf, sizeof(buf), "route add %s", 
                 inet_ntop (AF_INET, &pointopoint, ptr1, sizeof (ptr1)));
        /* avoid using a second buffer */
        di_snprintfcat(buf, sizeof(buf), "%s",
                       inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)));
        rv |= di_exec_shell_log(buf);
    } else if (gateway.s_addr) {
        snprintf(buf, sizeof(buf), "route add default %s",
                 inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1)));
        rv |= di_exec_shell_log(buf);
    }
#else
    deconfigure_network();

    loop_setup();
    interface_up(interface);

    /* Flush all previous addresses, routes */
    snprintf(buf, sizeof(buf), "ip addr flush dev %s", interface);
    rv |= di_exec_shell_log(buf);

    snprintf(buf, sizeof(buf), "ip route flush dev %s", interface);
    rv |= di_exec_shell_log(buf);

    rv |= !inet_ptom (NULL, &masksize, &netmask);

    /* Add the new IP address, P-t-P peer (if necessary) and netmask */
    snprintf(buf, sizeof(buf), "ip addr add %s/%d ",
             inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)),
             masksize);

    /* avoid using a second buffer */
    di_snprintfcat(buf, sizeof(buf), "broadcast %s dev %s",
                   inet_ntop (AF_INET, &broadcast, ptr1, sizeof (ptr1)),
                   interface);

    if (pointopoint.s_addr)
        di_snprintfcat(buf, sizeof(buf), " peer %s",
                       inet_ntop (AF_INET, &pointopoint, ptr1, sizeof (ptr1)));

    di_info("executing: %s", buf);
    rv |= di_exec_shell_log(buf);

    if (pointopoint.s_addr)
    {
        snprintf(buf, sizeof(buf), "ip route add default dev %s", interface);
        rv |= di_exec_shell_log(buf);
    }
    else if (gateway.s_addr) {
        snprintf(buf, sizeof(buf), "ip route add default via %s",
                 inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1)));
        rv |= di_exec_shell_log(buf);
    }
#endif

    if (rv != 0) {
        debconf_capb(client);
        debconf_input(client, "high", "netcfg/error");
        debconf_go(client);
        debconf_capb(client, "backup");
        return -1;
    }

    /* Wait to detect link.  Don't error out if we fail, though; link detection
     * may not work on this NIC or something.
     */
    netcfg_detect_link(client, interface);

    return 0;
}

int netcfg_get_static(struct debconfclient *client)
{
    char *nameservers = NULL;
    char ptr1[INET_ADDRSTRLEN];
    char *none;

    enum { BACKUP, GET_HOSTNAME, GET_IPADDRESS, GET_POINTOPOINT, GET_NETMASK,
           GET_GATEWAY, GATEWAY_UNREACHABLE, GET_NAMESERVERS, CONFIRM,
           GET_DOMAIN, QUIT }
    state = GET_IPADDRESS;

    ipaddress.s_addr = network.s_addr = broadcast.s_addr = netmask.s_addr = gateway.s_addr = pointopoint.s_addr =
        0;

    debconf_metaget(client,  "netcfg/internal-none", "description");
    none = client->value ? strdup(client->value) : strdup("<none>");

    for (;;) {
        switch (state) {
        case BACKUP:
            return 10; /* Back to main */
            break;

        case GET_IPADDRESS:
            if (netcfg_get_ipaddress (client)) {
                state = BACKUP;
            } else {
                if (strncmp(interface, "plip", 4) == 0
                    || strncmp(interface, "slip", 4) == 0
                    || strncmp(interface, "ctc", 3) == 0
                    || strncmp(interface, "escon", 5) == 0
                    || strncmp(interface, "iucv", 4) == 0)
                    state = GET_POINTOPOINT;
                else
                    state = GET_NETMASK;
            }
            break;

        case GET_POINTOPOINT:
            state = netcfg_get_pointopoint(client) ?
                GET_IPADDRESS : GET_NAMESERVERS;
            break;

        case GET_NETMASK:
            state = netcfg_get_netmask(client) ?
                GET_IPADDRESS : GET_GATEWAY;
            break;

        case GET_GATEWAY:
            if (netcfg_get_gateway(client))
                state = GET_NETMASK;
            else
                if (gateway.s_addr && ((gateway.s_addr & netmask.s_addr) != network.s_addr))
                    state = GATEWAY_UNREACHABLE;
                else
                    state = GET_NAMESERVERS;
            break;
        case GATEWAY_UNREACHABLE:
            debconf_capb(client); /* Turn off backup */
            debconf_input(client, "high", "netcfg/gateway_unreachable");
            debconf_go(client);
            state = GET_GATEWAY;
            debconf_capb(client, "backup");
            break;
        case GET_NAMESERVERS:
            state = (netcfg_get_nameservers (client, &nameservers)) ?
                GET_GATEWAY : CONFIRM;
            break;
        case GET_HOSTNAME:
            seed_hostname_from_dns(client, &ipaddress);
            state = (netcfg_get_hostname(client, "netcfg/get_hostname", &hostname, 1)) ?
                GET_NAMESERVERS : GET_DOMAIN;
            break;
        case GET_DOMAIN:
            if (!have_domain) {
                state = (netcfg_get_domain (client, &domain, "high")) ?
                    GET_HOSTNAME : QUIT;
            } else {
                di_info("domain = %s", domain);
                state = QUIT;
            }
            break;

        case CONFIRM:
            debconf_subst(client, "netcfg/confirm_static", "interface", interface);
            debconf_subst(client, "netcfg/confirm_static", "ipaddress",
                          (ipaddress.s_addr ? inet_ntop (AF_INET, &ipaddress, ptr1, sizeof (ptr1)) : none));
            debconf_subst(client, "netcfg/confirm_static", "pointopoint",
                          (pointopoint.s_addr ? inet_ntop (AF_INET, &pointopoint, ptr1, sizeof (ptr1)) : none));
            debconf_subst(client, "netcfg/confirm_static", "netmask",
                          (netmask.s_addr ? inet_ntop (AF_INET, &netmask, ptr1, sizeof (ptr1)) : none));
            debconf_subst(client, "netcfg/confirm_static", "gateway",
                          (gateway.s_addr ? inet_ntop (AF_INET, &gateway, ptr1, sizeof (ptr1)) : none));
            debconf_subst(client, "netcfg/confirm_static", "nameservers",
                          (nameservers ? nameservers : none));
            netcfg_nameservers_to_array(nameservers, nameserver_array);

            debconf_capb(client); /* Turn off backup for yes/no confirmation */

            debconf_input(client, "medium", "netcfg/confirm_static");
            debconf_go(client);
            debconf_get(client, "netcfg/confirm_static");
            if (strstr(client->value, "true")) {
                state = GET_HOSTNAME;
                netcfg_write_resolv(domain, nameserver_array);
                netcfg_activate_static(client);
            }
            else
                state = GET_IPADDRESS;

            debconf_capb(client, "backup");

            break;

        case QUIT:
            netcfg_write_common(ipaddress, hostname, domain);
            netcfg_write_static(domain, nameserver_array);
            return 0;
            break;
        }
    }
    return 0;
}
