/*
   netcfg-common.c - Shared functions used to configure the network for
   the debian-installer.

   Copyright (C) 2000-2002  David Kimdon <dwhedon@debian.org>
                            and others (see debian/copyright)

   This program is free software; 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 in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

#include "netcfg.h"
#if defined(WIRELESS)
#include <iwlib.h>
#endif
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdebconf/debconfclient.h>
#include <debian-installer.h>
#include <time.h>
#include <netdb.h>

#include <ifaddrs.h>

#ifdef __linux__
#include <netpacket/packet.h>
#define SYSCLASSNET "/sys/class/net/"
#endif /* __linux__ */

#ifdef __FreeBSD_kernel__
#include <net/if_dl.h>
#define LO_IF	"lo0"
#else
#define LO_IF	"lo"
#endif

/* network config */
char hostname[MAXHOSTNAMELEN + 1];
char domain[MAXHOSTNAMELEN + 1];
int have_domain = 0;

/* File descriptors for ioctls and such */
int skfd = 0;
#ifdef WIRELESS
int wfd = 0;
#endif

/* Count the number of contiguous 1 bits in a 32-bit integer, starting from
 * the MSB. */
static unsigned int count_bits(uint32_t num)
{
    int count = 0;

    while (num & 0x80000000) {
        count++;
        num <<= 1;
    }

    return count;
}

/* convert a netmask string (eg 255.255.255.0 or ffff:ff::) in +src+ into
 * the length (24) in +dst+.  Return 0 if some sort of failure, or 1 on
 * success.
 */
int inet_ptom (int af, const char *src, unsigned int *dst)
{
    union inX_addr addr;

    if (!empty_str(src)) {
        if (inet_pton (af, src, &addr) < 0) {
            *dst = 0;
            return 0;
        }
    }

    if (af == AF_INET) {
        *dst = count_bits(ntohl(addr.in4.s_addr));
        return 1;
    } else if (af == AF_INET6) {
        int i, count;
        for (i = 0, *dst = 0; i < 4; i++) {
            count = count_bits(htonl(addr.in6.s6_addr32[i]));
            *dst += count;
            if (count != 32) break;  /* Don't go any further if the mask has finished */
        }
        return 1;
    } else {
        *dst = 0;
        return 0;
    }
}

/* convert a length (24) in +src+ into the string netmask (255.255.255.0) in
 * +dst+.  The length of +dst+ is given in +len+, to ensure we don't
 * overrun the buffer +dst+.  +dst+ should always be at least NETCFG_ADDRSTRLEN
 * bytes long.
 *
 * Returns the address of +dst+ on success, and NULL on failure.
 */
const char *inet_mtop (int af, unsigned int src, char *dst, socklen_t len)
{
    struct in_addr addr;
    
    inet_mton(AF_INET, src, &addr);
    
    return inet_ntop (af, &addr, dst, len);
}

/* convert a mask length (eg 24) in +src+ into the struct in_addr it corresponds
 * to.
 */
void inet_mton (int af, unsigned int src, void *dst)
{
    in_addr_t mask = 0;
    struct in_addr *addr;
    struct in6_addr *addr6;
    
    if (af == AF_INET) {
        addr = (struct in_addr *)dst;
        for(; src; src--)
            mask |= 1 << (32 - src);

        addr->s_addr = htonl(mask);
    } else if (af == AF_INET6) {
        unsigned int byte = 0;
        addr6 = (struct in6_addr *)dst;
        /* Clear out the address */
        memset(addr6->s6_addr, 0, 16);
        
        while (src > 7) {
            addr6->s6_addr[byte++] = 0xff;
            src -= 8;
        }
        for (; src; src--)
            addr6->s6_addr[byte] |= 1 << (8 - src);
    }
}

void open_sockets (void)
{
#ifdef WIRELESS
    wfd = iw_sockets_open();
#endif
    skfd = socket (AF_INET, SOCK_DGRAM, 0);
}

#ifdef __linux__

/* Returns non-zero if this interface has an enabled kill switch, otherwise
 * zero.
 */
int check_kill_switch(const char *if_name)
{
    char *temp, *linkbuf;
    const char *killname;
    char killstate;
    size_t len;
    int linklen, killlen;
    int fd = -1;
    int ret = 0;

    /* longest string we need */
    len = strlen(SYSCLASSNET) + strlen(if_name) + strlen("/device/rf_kill") + 1;

    temp = malloc(len);
    snprintf(temp, len, SYSCLASSNET "%s/driver", if_name);
    linkbuf = malloc(1024); /* probably OK ... I hate readlink() */
    linklen = readlink(temp, linkbuf, 1024);
    if (linklen < 0)
        goto out;

    if (strncmp(linkbuf + linklen - 8, "/ipw2100", 8) == 0)
        killname = "rf_kill";
    else if (strncmp(linkbuf + linklen - 8, "/ipw2200", 8) == 0)
        killname = "rf_kill";
    else
        goto out;

    snprintf(temp, len, SYSCLASSNET "%s/device/%s", if_name, killname);
    di_info("Checking RF kill switch: %s", temp);
    fd = open(temp, O_RDONLY);
    if (fd == -1)
        goto out;
    killlen = read(fd, &killstate, 1);
    if (killlen < 0) {
        di_error("Failed to read RF kill state: %s", strerror(errno));
        goto out;
    } else if (killlen == 0) {
        di_warning("RF kill state file empty");
        goto out;
    }

    if (killstate == '2') {
        di_info("RF kill switch enabled");
        ret = 1;
    }

 out:
    free(temp);
    free(linkbuf);

    if (fd != -1)
        close(fd);

    return ret;
}

#else /* !__linux__ */
int check_kill_switch(const char *if_name)
{
    (void)if_name;
    return 0;
}
#endif /* __linux__ */

#if defined(WIRELESS)
int is_raw_80211(const char *iface)
{
    struct ifreq ifr;
    struct sockaddr sa;

    strncpy(ifr.ifr_name, iface, IFNAMSIZ-1);
    ifr.ifr_name[IFNAMSIZ-1]='\0';

    if (skfd && ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) {
        di_warning("Unable to retrieve interface type.");
        return 0;
    }

    sa = * (struct sockaddr *) &ifr.ifr_hwaddr;
    switch (sa.sa_family) {
    case ARPHRD_IEEE80211:
    case ARPHRD_IEEE80211_PRISM:
    case ARPHRD_IEEE80211_RADIOTAP:
        return 1;

    default:
        return 0;
    }
}
#endif

#if defined(__s390__)
// Layer 3 qeth on s390(x) cannot do arping to test gateway reachability.
int is_layer3_qeth(const struct netcfg_interface *interface)
{
    const int bufsize = 1024;
    int retval = 0;
    char* path;
    char* buf;
    size_t len;
    ssize_t slen;
    char* driver;
    int fd;
    char* iface;

    if (interface->parentif)
        iface = interface->parentif;
    else
        iface = interface->name;

    // This is sufficient for both /driver and /layer2.
    len = strlen(SYSCLASSNET) + strlen(iface) + strlen("/device/driver") + 1;

    path = malloc(len);
    snprintf(path, len, SYSCLASSNET "%s/device/driver", iface);

    // lstat() on sysfs symlinks does not provide size information.
    buf = malloc(bufsize);
    slen = readlink(path, buf, bufsize - 1);

    if (slen < 0) {
        di_error("Symlink %s cannot be resolved: %s", path, strerror(errno));
        goto out;
    }

    buf[slen] = '\0';

    driver = strrchr(buf, '/') + 1;
    if (strcmp(driver, "qeth") != 0) {
        di_info("no qeth found: %s", driver);
        goto out;
    }

    snprintf(path, len, SYSCLASSNET "%s/device/layer2", iface);

    fd = open(path, O_RDONLY);
    if (fd == -1) {
        di_error("%s cannot be opened: %s", path, strerror(errno));
        goto out;
    }

    slen = read(fd, buf, 1);
    if (slen == -1) {
        di_error("Read from %s failed: %s", path, strerror(errno));
        close(fd);
        goto out;
    }

    if (buf[0] == '0') {
        // driver == 'qeth' && layer2 == 0
        retval = 1;
    }

    close(fd);

out:
    free(buf);
    free(path);
    return retval;
}
#else
int is_layer3_qeth(const struct netcfg_interface *interface  __attribute__((unused)))
{
    return 0;
}
#endif

int qsort_strcmp(const void *a, const void *b)
{
    const char **ia = (const char **)a;
    const char **ib = (const char **)b;
    return strcmp(*ia, *ib);
}

#ifdef __GNU__
#include <mach.h>
#include <device/device.h>
#include <hurd.h>
/* On Hurd, the IP stack (pfinet) does not know the list of network interfaces
 * before we configure them, so we cannot use getifaddrs(). Instead we try
 * possible names for network interfaces and check whether they exists by
 * attempting to open the kernel device. */
int get_all_ifs (int all __attribute__ ((unused)), char*** ptr)
{
    static const char *const fmt[] = { "eth%d", "wl%d", NULL };

    mach_port_t device_master, file_master;
    device_t device;
    int err;
    char **list;
    int num, i, j;
    char name[3 + 3 * sizeof (int) + 1];
    char devname[5 + sizeof(name)];

    err = get_privileged_ports (0, &device_master);
    if (err)
        return 0;

    num = 0;
    list = malloc(sizeof *list);
    for (i = 0; fmt[i]; i++)
        for (j = 0;; j++) {
            char *thename;
            sprintf (name, fmt[i], j);
            sprintf (devname, "/dev/%s", name);
            err = device_open (device_master, D_READ, name, &device);
            if (err == 0)
                thename = name;
            else
                {
                    file_master = file_name_lookup (devname, O_READ | O_WRITE, 0);
                    if (file_master == MACH_PORT_NULL)
                        break;

                    err = device_open (file_master, D_READ, name, &device);
                    mach_port_deallocate (mach_task_self (), file_master);
                    if (err != 0)
                        break;
                    thename = devname;
                }

            device_close (device);
            mach_port_deallocate (mach_task_self (), device);

            list = realloc (list, (num + 2) * sizeof *list);
            list[num++] = strdup(thename);
        }
    list[num] = NULL;

    mach_port_deallocate (mach_task_self (), device_master);
    *ptr = list;
    return num;
}
#else
int get_all_ifs (int all, char*** ptr)
{
    struct ifaddrs *ifap, *ifa;
    char ibuf[512];
    char** list = NULL;
    size_t len = 0;
    ibuf[sizeof(ibuf)-1]='\0';

    if (getifaddrs(&ifap) == -1)
        return 0;

    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
        strncpy(ibuf, ifa->ifa_name, sizeof(ibuf)-1);
        if (ifa->ifa_flags & IFF_LOOPBACK)   /* ignore loopback devices */
            continue;
#if defined(__linux__)
        if (!strncmp(ibuf, "sit", 3))        /* ignore tunnel devices */
            continue;
#endif
#if defined(__FreeBSD_kernel__)
        if (!strncmp(ibuf, "pfsync", 6))     /* ignore pfsync devices */
            continue;
        if (!strncmp(ibuf, "pflog", 5))      /* ignore pflog devices */
            continue;
        if (!strncmp(ibuf, "usbus", 5))      /* ignore usbus devices */
            continue;
#endif
#if defined(WIRELESS)
        if (is_raw_80211(ibuf))
            continue;
#endif
        if (all || ifa->ifa_flags & IFF_UP) {
            int found = 0;
            size_t i;

            for (i = 0 ; i < len ; i++) {
                if (!strcmp(ibuf, list[i])) {
                    found = 1;
                }
            }
            if (!found) {
                list = realloc(list, sizeof(char*) * (len + 2));
                list[len] = strdup(ibuf);
                len++;
            }
        }
    }

    /* OK, now sort the list and terminate it if necessary */
    if (list != NULL) {
        qsort(list, len, sizeof(char *), qsort_strcmp);
        list[len] = NULL;
    }
    freeifaddrs(ifap);

    *ptr = list;

    return len;
}
#endif

#ifdef __linux__
short find_in_stab(const char *if_name)
{
    FILE *dn = NULL;
    char buf[128];
    size_t len = strlen(if_name);

    if (access(STAB, F_OK) == -1)
        return 0;

    if (!(dn = popen("grep -v '^Socket' " STAB " | cut -f5", "r")))
        return 0;

    while (fgets (buf, 128, dn) != NULL) {
        if (!strncmp(buf, if_name, len)) {
            pclose(dn);
            return 1;
        }
    }
    pclose(dn);
    return 0;
}
#else /* !__linux__ */
/* Stub function for platforms not supporting /var/run/stab. */
short find_in_stab(const char *if_name)
{
    (void)if_name;
    return 0;
}
#endif /* __linux__ */

char *find_in_devnames(const char* iface)
{
    FILE* dn = NULL;
    char buf[512], *result = NULL;
    size_t len = strlen(iface);

    if (!(dn = fopen(DEVNAMES, "r")))
        return NULL;

    while (fgets(buf, 512, dn) != NULL) {
        char *ptr = strchr(buf, ':'), *desc = ptr + 1;

        if (!ptr) {
            result = NULL; /* corrupt */
            break;
        }
        else if (!strncmp(buf, iface, len)) {
            result = strdup(desc);
            break;
        }
    }

    fclose(dn);

    if (result) {
        len = strlen(result);

        if (result[len - 1] == '\n')
            result[len - 1] = '\0';
    }

    return result;
}

char *get_ifdsc(struct debconfclient *client, const char *if_name)
{
    char template[256], *ptr = NULL;

    if ((ptr = find_in_devnames(if_name)) != NULL) {
        debconf_metaget(client, "netcfg/internal-wireless", "description");

        if (is_wireless_iface(if_name)) {
            size_t len = strlen(ptr) + strlen(client->value) + 4;
            ptr = realloc(ptr, len);

            di_snprintfcat(ptr, len, " (%s)", client->value);
        }
        return ptr; /* already strdup'd */
    }

    if (strlen(if_name) < 100) {
        if (!is_wireless_iface(if_name)) {
            /* strip away the number from the interface (eth0 -> eth) */
            char *ifp = strdup(if_name), *ptr = ifp;
            while ((*ptr < '0' || *ptr > '9') && *ptr != '\0')
                ptr++;
            *ptr = '\0';

            sprintf(template, "netcfg/internal-%s", ifp);
            free(ifp);

            if (debconf_metaget(client, template, "description") ==
                    CMD_SUCCESS && client->value != NULL) {
                return strdup(client->value);
            }
	    /* If using systemd-udevd stable names try that as well
	       Those have two character prefix, then one character for
	       subtype. */
	    if (strlen(template) > 19) {
		template[19] = '\0';
		if (debconf_metaget(client, template, "description") ==
                    CMD_SUCCESS && client->value != NULL) {
		    return strdup(client->value);
		}
	    }
        } else {
            strcpy(template, "netcfg/internal-wifi");
            debconf_metaget(client, template, "description");
            return strdup(client->value);
        }
    }
    debconf_metaget(client, "netcfg/internal-unknown-iface", "description");
    if (client->value != NULL)
        return strdup(client->value);
    else
        return strdup("Unknown interface");
}

int iface_is_hotpluggable(const char *if_name)
{
    FILE* f = NULL;
    char buf[256];
    size_t len = strlen(if_name);

    if (!(f = fopen(DEVHOTPLUG, "r"))) {
        di_info("No hotpluggable devices are present in the system.");
        return 0;
    }

    while (fgets(buf, 256, f) != NULL) {
        if (!strncmp(buf, if_name, len)) {
            di_info("Detected %s as a hotpluggable device", if_name);
            fclose(f);
            return 1;
        }
    }

    fclose(f);

    di_info("Hotpluggable devices available, but %s is not one of them", if_name);
    return 0;
}

FILE *file_open(char *path, const char *opentype)
{
    FILE *fp;
    if ((fp = fopen(path, opentype)))
        return fp;
    else {
        fprintf(stderr, "%s\n", path);
        perror("fopen");
        return NULL;
    }
}

static char *get_bootif(void)
{
#ifdef __linux__
#define PROC_CMDLINE "/proc/cmdline"

    FILE *cmdline_file;
    char *cmdline = NULL;
    size_t dummy;
    const char *s;
    char *bootif = NULL;

    /* Look for BOOTIF= entry in kernel command line. */

    cmdline_file = file_open(PROC_CMDLINE, "r");
    if (!cmdline_file) {
        di_error("Failed to open " PROC_CMDLINE ": %s", strerror(errno));
        return NULL;
    }
    if (getline(&cmdline, &dummy, cmdline_file) < 0) {
        di_error("Failed to read line from " PROC_CMDLINE ": %s",
                 strerror(errno));
        fclose(cmdline_file);
        return NULL;
    }

    s = cmdline;
    while ((s = strstr(s, "BOOTIF=")) != NULL) {
        if (s == cmdline || s[-1] == ' ') {
            size_t bootif_len;
            char *subst;

            s += sizeof("BOOTIF=") - 1;
            bootif_len = strcspn(s, " \n");
            if (bootif_len != (ETH_ALEN * 3 - 1) + 3)
                continue;
            bootif = strndup(s + 3, bootif_len - 3); /* skip hardware type */
            for (subst = bootif; *subst; subst++)
                if (*subst == '-')
                    *subst = ':';
            break;
        }
        s++;
    }

    free(cmdline);
    fclose(cmdline_file);

    if (!bootif)
        di_info("Could not find valid BOOTIF= entry in " PROC_CMDLINE);

    return bootif;

#undef PROC_CMDLINE
#else /* !__linux__ */
    return NULL;
#endif /* __linux__ */
}

static unsigned char *parse_bootif(const char *bootif, int quiet)
{
    int i;
    const char *s;
    unsigned char *bootif_addr = malloc(ETH_ALEN);

    /* Parse supplied address. */
    for (i = 0, s = bootif; i < ETH_ALEN && s; i++) {
        unsigned long bootif_byte;

        errno = 0;
        bootif_byte = strtol(s, (char **) &s, 16);
        if (errno || bootif_byte >= 256) {
            if (!quiet)
                di_error("couldn't parse link-layer address '%s'", bootif);
            free(bootif_addr);
            return NULL;
        }
        bootif_addr[i] = (unsigned char) bootif_byte;
        if (i < ETH_ALEN - 1 && *s++ != ':') {
            if (!quiet)
                di_error("couldn't parse link-layer address '%s'", bootif);
            free(bootif_addr);
            return NULL;
        }
    }

    return bootif_addr;
}

static char *find_bootif_iface(const char *bootif,
                               const unsigned char *bootif_addr)
{
#ifdef __GNU__
    /* TODO: Use device_get_status(NET_ADDRESS), see pfinet/ethernet.c */
    (void)bootif;
    (void)bootif_addr;
    return NULL;
#else
    struct ifaddrs *ifap, *ifa;
    char *ret = NULL;

    /* TODO: this won't work on the Hurd as getifaddrs doesn't return
     * unconfigured interfaces.  See comment to get_all_ifs.
     */
    if (getifaddrs(&ifap) < 0) {
        di_error("getifaddrs failed: %s", strerror(errno));
        return NULL;
    }

    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
#if defined(__FreeBSD_kernel__)
        struct sockaddr_dl *sdl;
#else
        struct sockaddr_ll *sll;
#endif

        if (ifa->ifa_flags & IFF_LOOPBACK)
            continue;
#if defined(__linux__)
        if (!strncmp(ifa->ifa_name, "sit", 3))  /* ignore tunnel devices */
            continue;
#endif
#if defined(WIRELESS)
        if (is_raw_80211(ifa->ifa_name))
            continue;
#endif
#if defined(__FreeBSD_kernel__)
        if (ifa->ifa_addr->sa_family != AF_LINK)
            continue;
        sdl = (struct sockaddr_dl *) ifa->ifa_addr;
        if (!sdl)                               /* no link-layer address */
            continue;
        if (sdl->sdl_alen != ETH_ALEN)          /* not Ethernet */
            continue;
        if (memcmp(bootif_addr, LLADDR(sdl), ETH_ALEN) != 0)
            continue;
#else
        if (ifa->ifa_addr->sa_family != AF_PACKET)
            continue;
        sll = (struct sockaddr_ll *) ifa->ifa_addr;
        if (!sll)                               /* no link-layer address */
            continue;
        if ((sll->sll_hatype != ARPHRD_ETHER &&
             sll->sll_hatype != ARPHRD_IEEE802) ||
            sll->sll_halen != ETH_ALEN)         /* not Ethernet */
            continue;
        if (memcmp(bootif_addr, sll->sll_addr, ETH_ALEN) != 0)
            continue;
#endif

        di_info("Found interface %s with link-layer address %s",
                ifa->ifa_name, bootif);
        ret = strdup(ifa->ifa_name);
        break;
    }

    freeifaddrs(ifap);

    if (!ret)
        di_error("Could not find any interface with address %s", bootif);

    return ret;
#endif
}

void netcfg_die(struct debconfclient *client)
{
    debconf_progress_stop(client);
    debconf_capb(client);
    debconf_input(client, "high", "netcfg/error");
    debconf_go(client);
    exit(1);
}

/**
 * @brief Ask which interface to configure
 * @param client    - client
 * @param interface - set the +name+ field to the answer
 * @param numif     - number of interfaces found.
 * @param defif     - default interface from link detection.
 */

int netcfg_get_interface(struct debconfclient *client, char **interface,
                         int *numif, const char *defif)
{
    char *inter = NULL, **ifs;
    size_t len;
    int ret, i, asked;
    int num_interfaces = 0;
    unsigned char *bootif_addr;
    char *bootif_iface = NULL;
    char *ptr = NULL;
    char *ifdsc = NULL;
    char *old_selection = NULL;

    if (*interface) {
        free(*interface);
        *interface = NULL;
    }

    if (!(ptr = malloc(128)))
        goto error;

    len = 128;
    *ptr = '\0';

    num_interfaces = get_all_ifs(1, &ifs);

    /* Remember old interface selection, in case it's preseeded. */
    debconf_get(client, "netcfg/choose_interface");
    old_selection = strdup(client->value);

    /* If netcfg/choose_interface is preseeded to a link-layer address in
     * the form aa:bb:cc:dd:ee:ff, or if BOOTIF is set and matches an
     * interface, override any provided default from link detection. */
    bootif_addr = parse_bootif(old_selection, 1);
    if (bootif_addr) {
        bootif_iface = find_bootif_iface(old_selection, bootif_addr);
        if (bootif_iface) {
            free(old_selection);
            old_selection = strdup(bootif_iface);
        }
        free(bootif_addr);
    } else {
        char *bootif = get_bootif();
        if (bootif) {
            bootif_addr = parse_bootif(bootif, 0);
            if (bootif_addr) {
                bootif_iface = find_bootif_iface(bootif, bootif_addr);
                free(bootif_addr);
            }
            free(bootif);
        }
    }
    if (bootif_iface) {
        /* Did we actually get back an interface we know about? */
        for (i = 0; i < num_interfaces; i++) {
            if (strcmp(ifs[i], bootif_iface) == 0) {
                defif = ifs[i];
                break;
            }
        }
        free (bootif_iface);
    }

    /* If no default was provided, use the first in the list of interfaces. */
    if (! defif && num_interfaces > 0) {
        defif = ifs[0];
    }

    for (i = 0; i < num_interfaces; i++) {
        size_t newchars;
        char *temp = NULL;

        inter = ifs[i];

        interface_down(inter);
        ifdsc = get_ifdsc(client, inter);
        newchars = strlen(inter) + strlen(ifdsc) + 5; /* ": , " + NUL */
        if (len < (strlen(ptr) + newchars)) {
            if (!(ptr = realloc(ptr, len + newchars + 128)))
                goto error;
            len += newchars + 128;
        }

        temp = malloc(newchars);

        snprintf(temp, newchars, "%s: %s", inter, ifdsc);

        if (num_interfaces > 1 &&
            ((strcmp(defif, inter) == 0) || (strcmp(defif, temp) == 0)))
            debconf_set(client, "netcfg/choose_interface", temp);

        di_snprintfcat(ptr, len, "%s, ", temp);

        free(temp);
        free(ifdsc);
    }

    if (num_interfaces == 0) {
        debconf_input(client, "high", "netcfg/no_interfaces");
        ret = debconf_go(client);
        free(ptr);
        free(old_selection);
        *numif = 0;

        return ret;
    }
    else if (num_interfaces == 1) {
        inter = ptr;
        *numif = 1;
        free(old_selection);
    }
    else if (num_interfaces > 1) {
        *numif = num_interfaces;
        /* remove the trailing ", ", which confuses cdebconf */
        ptr[strlen(ptr) - 2] = '\0';

        debconf_subst(client, "netcfg/choose_interface", "ifchoices", ptr);
        free(ptr);

        asked = (debconf_input(client, "critical", "netcfg/choose_interface") == CMD_SUCCESS);
        ret = debconf_go(client);

        /* If the question is not asked, honor preseeded interface name.
         * However, if it was preseeded to "auto", or there was no old value,
         * leave it set to defif. */
        if (!asked && strlen(old_selection) && strcmp(old_selection, "auto") != 0) {
            debconf_set(client, "netcfg/choose_interface", old_selection);
        }

        free(old_selection);

        if (ret)
            return ret;

        debconf_get(client, "netcfg/choose_interface");
        inter = client->value;

        if (!inter)
            netcfg_die(client);
    }

    /* grab just the interface name, not the description too */
    *interface = inter;
    /* Note that the question may be preseeded to just the interface name,
     * with no colon after it. Allow for this case. */
    ptr = strchr(inter, ':');
    if (ptr != NULL) {
        *ptr = '\0';
    }
    *interface = strdup(*interface);

    /* Free allocated memory */
    while (ifs && *ifs)
        free(*ifs++);

    return 0;

 error:
    if (ptr)
        free(ptr);

    netcfg_die(client);
    return RETURN_TO_MAIN; /* unreachable */
}

/*
 * Verify that the hostname conforms to RFC 1123 s2.1,
 * and RFC 1034 s3.5.
 * @return 1 on success, 0 on failure.
 */
short valid_hostname (const char *hname)
{
    static const char *valid_chars =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
    size_t len;
    assert(hname != NULL);

    len = strlen(hname);

    if ((len < 1) ||
        (len > MAXHOSTNAMELEN) ||
        (strspn(hname, valid_chars) != len) ||
        (hname[len - 1] == '-') ||
        (hname[0] == '-')) {
        return 0;
    }
    else
        return 1;
}

/*
 * Verify that the domain name (or FQDN) conforms to RFC 1123 s2.1, and
 * RFC1034 s3.5.
 * @return 1 on success, 0 on failure.
 */
short valid_domain (const char *dname)
{
    static const char *valid_chars =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.";
    size_t len;
    assert(dname != NULL);

    len = strlen(dname);

    if ((len < 1) ||
        (len > MAXHOSTNAMELEN) ||
        (strspn(dname, valid_chars) != len) ||
        (dname[len - 1] == '-') ||
        (dname[0] == '-') ||
        (dname[len - 1] == '.') ||
        (dname[0] == '.') ||
        strstr(dname, "..")) {
        return 0;
    }
    else
        return 1;
}

/*
 * Write the hostname to the given string (must be capable of storing at
 * least MAXHOSTNAMELEN bytes.
 *
 * @return 0 on success, 30 on BACKUP being selected.
 */
int netcfg_get_hostname(struct debconfclient *client, char *template, char *hostname, short accept_domain)
{
    char *s, buf[1024];

    for(;;) {
        if (accept_domain)
            have_domain = 0;
        debconf_input(client, "high", template);

        if (debconf_go(client) == CMD_GOBACK)
            return GO_BACK;

        debconf_get(client, template);

        strncpy(hostname, client->value, MAXHOSTNAMELEN);

        if (!valid_domain(hostname)) {
            di_info("%s is an invalid domain", hostname);
            debconf_subst(client, "netcfg/invalid_hostname",
                          "hostname", hostname);
            snprintf(buf, sizeof(buf), "%i", MAXHOSTNAMELEN);
            debconf_subst(client, "netcfg/invalid_hostname",
                      "maxhostnamelen", buf);
            debconf_input(client, "high", "netcfg/invalid_hostname");
            debconf_go(client);
            debconf_set(client, template, "ubuntu");
            *hostname = '\0';
            continue;
        }

        if (accept_domain && (s = strchr(hostname, '.'))) {
            di_info("Detected we have an FQDN; splitting and setting domain");
            if (s[1] == '\0') { /* "somehostname." <- . should be ignored */
                *s = '\0';
            } else { /* assume we have a valid domain name given */
                strncpy(domain, s + 1, MAXHOSTNAMELEN);
                debconf_set(client, "netcfg/get_domain", domain);
                have_domain = 1;
                *s = '\0';
            }
        }
        
        if (!valid_hostname(hostname)) {
            di_info("%s is an invalid hostname", hostname);
            debconf_subst(client, "netcfg/invalid_hostname",
                          "hostname", hostname);
            snprintf(buf, sizeof(buf), "%i", MAXHOSTNAMELEN);
            debconf_subst(client, "netcfg/invalid_hostname",
                      "maxhostnamelen", buf);
            debconf_input(client, "high", "netcfg/invalid_hostname");
            debconf_go(client);
            debconf_set(client, template, "ubuntu");
            *hostname = '\0';
        } else {
            break;
        }
    }

    return 0;
}

/* @brief Get the domainname.
 * @return 0 for success, with *domain = domain, GO_BACK for 'goback',
 */
int netcfg_get_domain(struct debconfclient *client,  char domain[], const char *priority)
{
    int ret;

    if (have_domain == 1)
    {
        debconf_get(client, "netcfg/get_domain");
        assert (!empty_str(client->value));
        strncpy(domain, client->value, MAXHOSTNAMELEN);
        return 0;
    }

    debconf_input (client, priority, "netcfg/get_domain");
    ret = debconf_go(client);

    if (ret)
        return ret;

    debconf_get (client, "netcfg/get_domain");

    *domain = '\0';
    if (!empty_str(client->value)) {
        const char *start = client->value;
        while (*start == '.')
            ++start; /* trim leading dots */
        strncpy(domain, start, MAXHOSTNAMELEN);
    }

    return 0;
}

void netcfg_write_loopback (void)
{
    struct netcfg_interface lo;
    
    netcfg_interface_init(&lo);
    lo.name = LO_IF;
    lo.loopback = 1;
    
    netcfg_write_interface(NULL, NULL, NULL);
    netcfg_write_interface(NULL, &lo, NULL);
}

/*
 * ipaddress.s_addr may be 0
 * domain may be null
 * interface may be null
 * hostname may _not_ be null
 */
void netcfg_write_common(const char *ipaddress, const char *hostname, const char *domain)
{
    FILE *fp;
    char *domain_nodot = NULL;

    if (empty_str(hostname))
        return;

    if (domain) {
        char *end;

        /* strip trailing dots */
        domain_nodot = strdup(domain);
        end = domain_nodot + strlen(domain_nodot) - 1;
        while (end >= domain_nodot && *end == '.')
            *end-- = '\0';
    }

    /* Currently busybox, hostname is not available. */
    if (sethostname (hostname, strlen(hostname) + 1) < 0) {
        /* ignore errors */
    }

    if ((fp = file_open(HOSTNAME_FILE, "w"))) {
        fprintf(fp, "%s\n", hostname);
        fclose(fp);
    }

    if ((fp = file_open(HOSTS_FILE, "w"))) {
        fprintf(fp, "127.0.0.1\tlocalhost");

        if (!empty_str(ipaddress)) {
            if (domain_nodot && !empty_str(domain_nodot))
                fprintf(fp, "\n%s\t%s.%s\t%s\n", ipaddress, hostname, domain_nodot, hostname);
            else
                fprintf(fp, "\n%s\t%s\n", ipaddress, hostname);
        } else {
#if defined(__linux__) || defined(__GNU__)
            if (domain_nodot && !empty_str(domain_nodot))
                fprintf(fp, "\n127.0.1.1\t%s.%s\t%s\n", hostname, domain_nodot, hostname);
            else
                fprintf(fp, "\n127.0.1.1\t%s\n", hostname);
#else
            fprintf(fp, "\t%s\n", hostname);
#endif
        }

        fprintf(fp, "\n" IPV6_HOSTS);

        fclose(fp);
    }

    free(domain_nodot);
}


void deconfigure_network(struct netcfg_interface *iface)
{
    /* deconfiguring network interfaces */
    interface_down(LO_IF);
    if (iface)
        interface_down(iface->name);
}

void loop_setup(void)
{
    static int afpacket_notloaded = 1;

    deconfigure_network(NULL);

#if defined(__FreeBSD_kernel__)
    (void)afpacket_notloaded;
    /* GNU/kFreeBSD currently uses the ifconfig command */
    di_exec_shell_log("ifconfig "LO_IF" up");
    di_exec_shell_log("ifconfig "LO_IF" 127.0.0.1 netmask 255.0.0.0");
#else
    if (afpacket_notloaded)
        afpacket_notloaded = di_exec_shell("modprobe af_packet"); /* should become 0 */

    di_exec_shell_log("ip link set "LO_IF" up");
    di_exec_shell_log("ip -f inet addr flush dev "LO_IF);
    di_exec_shell_log("ip addr add 127.0.0.1/8 dev "LO_IF);
#endif
}

/* Determines the IP address of the interface (either from the static
 * configuration, or by querying the interface directly if using some sort
 * of autoconfiguration), then uses that address to request rDNS lookup
 * using the currently configured nameservers.  We return the name in
 * +hostname+ if one is found, and return 1, otherwise we leave the
 * +hostname+ alone and return 0.
 */
int get_hostname_from_dns (const struct netcfg_interface *interface, char *hostname, const size_t max_hostname_len)
{
    int err = 1;
    
    if (!empty_str(interface->ipaddress)) {
        /* Static configuration assumed */
        struct sockaddr_in sin;
        struct sockaddr_in6 sin6;

        di_debug("Getting default hostname from rDNS lookup of static-configured address %s", interface->ipaddress);
        if (interface->address_family == AF_INET) {
            sin.sin_family = AF_INET;
            sin.sin_port = 0;
            inet_pton(AF_INET, interface->ipaddress, &sin.sin_addr);
            err = getnameinfo((struct sockaddr *) &sin, sizeof(sin),
                              hostname, max_hostname_len, NULL, 0, NI_NAMEREQD);
        } else if (interface->address_family == AF_INET6) {
            sin6.sin6_family = AF_INET6;
            sin6.sin6_port = 0;
            inet_pton(AF_INET6, interface->ipaddress, &sin6.sin6_addr);
            err = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6),
                              hostname, max_hostname_len, NULL, 0, NI_NAMEREQD);
        } else {
            di_warning("Unknown address family in interface passed to seed_hostname_from_dns(): %i", interface->address_family);
            return 0;
        }

        if (err) {
            di_debug("getnameinfo() returned %i (%s)", err, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
        }

        if (err == 0) {
            /* We found a name!  We found a name! */
            di_debug("Hostname found: %s", hostname);
        }
    } else {
        /* Autoconfigured interface; we need to find the IP address ourselves
         */
        struct ifaddrs *ifa_head, *ifa;
        char tmpbuf[NETCFG_ADDRSTRLEN];
        
        if (getifaddrs(&ifa_head) == -1) {
            di_warning("getifaddrs() failed: %s", strerror(errno));
            return 0;
        }
        
        for (ifa = ifa_head; ifa != NULL; ifa = ifa->ifa_next) {
            if (strcmp(ifa->ifa_name, interface->name) != 0) {
                /* This isn't the interface you're looking for */
                continue;
            }
            if (!ifa->ifa_addr) {
                /* This isn't even a record with an address... bugger that */
                continue;
            }
            if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) {
                /* Not an IPv4 or IPv6 address... don't know what to do with it */
                continue;
            }

            di_debug("Getting default hostname from rDNS lookup of autoconfigured address %s",
                     inet_ntop(ifa->ifa_addr->sa_family,
                               (ifa->ifa_addr->sa_family == AF_INET) ?
                                (void *)(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr)
                                : (void *)(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr),
                               tmpbuf, sizeof(tmpbuf)
                              )
                    );
            err = getnameinfo(ifa->ifa_addr,
                              (ifa->ifa_addr->sa_family == AF_INET) ?
                                    sizeof(struct sockaddr_in)
                                    : sizeof(struct sockaddr_in6),
                              hostname, max_hostname_len, NULL, 0, NI_NAMEREQD);
            if (err) {
                di_debug("getnameinfo() returned %i (%s)", err, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
            }
                              
            if (err == 0) {
                /* We found a name!  We found a name! */
                di_debug("Hostname found: %s", hostname);
                break;
            }
        }
    }
            
    return !err;
}

void interface_up (const char *if_name)
{
    struct ifreq ifr;

    strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
    ifr.ifr_name[IFNAMSIZ-1]='\0';

    if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >= 0) {
        di_info("Activating interface %s", if_name);
        strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
        ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
        ioctl(skfd, SIOCSIFFLAGS, &ifr);
    } else {
        di_info("Getting flags for interface %s failed, not activating interface.", if_name);
    }
}

void interface_down (const char *if_name)
{
    struct ifreq ifr;

    strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
    ifr.ifr_name[IFNAMSIZ-1]='\0';

    if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >= 0) {
        di_info("Taking down interface %s", if_name);
        strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
        ifr.ifr_flags &= ~IFF_UP;
        ioctl(skfd, SIOCSIFFLAGS, &ifr);
    } else {
        di_info("Getting flags for interface %s failed, not taking down interface.", if_name);
    }
}

void netcfg_interface_up (const struct netcfg_interface *iface)
{
	if (iface->parentif)
		interface_up (iface->parentif);

	if (iface->name)
		interface_up (iface->name);
}

void netcfg_interface_down (const struct netcfg_interface *iface)
{
	if (iface->name)
		interface_down (iface->name);

	if (iface->parentif)
		interface_down (iface->parentif);
}

void parse_args (int argc, char ** argv)
{
    if (argc == 2) {
        if (!strcmp(basename(argv[0]), "ptom")) {
            unsigned int ret;
            if (inet_ptom(AF_INET, argv[1], &ret) > 0) {
                printf("%d\n", ret);
                exit(EXIT_SUCCESS);
            }
        }
        if (!strcmp(argv[1], "write_loopback")) {
            netcfg_write_loopback();
            exit(EXIT_SUCCESS);
        }

        exit(EXIT_FAILURE);
    }
}

void reap_old_files (void)
{
    static char* remove[] =
        { INTERFACES_FILE, HOSTS_FILE, HOSTNAME_FILE, NETWORKS_FILE,
          DHCLIENT_CONF, DOMAIN_FILE, 0 };
    char **ptr = remove;

    while (*ptr)
        unlink(*ptr++);

    if (!is_resolvconf_used())
	    if (!is_resolved_used())
		unlink(RESOLV_FILE);
}

/* Convert a space-separated list of nameservers in a single string (as might
 * be entered into, say, debconf), and store them in the interface.
 */
void netcfg_nameservers_to_array(const char *nameservers, struct netcfg_interface *interface)
{
    char *save, *ptr, *ns;
    unsigned int i;
    union inX_addr addr;

    if (nameservers) {
        save = ptr = strdup(nameservers);

        for (i = 0; i < NETCFG_NAMESERVERS_MAX; i++) {
            int af;
            ns = strtok_r(ptr, " \n\t", &ptr);
            if (ns) {
                /* The double conversion here is to both validate that we've
                 * been given an IP address, and to ensure that the address
                 * is in it's canonical form and will fit in the size of the
                 * array element provided.
                 */
                if (inet_pton (AF_INET, ns, &addr)) {
                    /* v4! */
                    af = AF_INET;
                } else {
                    /* v6? */
                    if (inet_pton (AF_INET6, ns, &addr)) {
                        af = AF_INET6;
                    } else {
                        af = -1;
                        fprintf(stderr, "Failed to parse %s as an IP address", ns);
                    }
                }

                if (af != -1) {
                    inet_ntop (af, &addr, interface->nameservers[i], NETCFG_ADDRSTRLEN);
                } else {
                    /* Dud in this slot; empty it */
                    *(interface->nameservers[i]) = '\0';
                }
            } else
                *(interface->nameservers[i]) = '\0';
        }

        free(save);
    } else {
        /* Empty out all the nameserver strings */
        for (i = 0; i < NETCFG_NAMESERVERS_MAX; i++) *(interface->nameservers[i]) = '\0';
    }
}

int netcfg_get_nameservers (struct debconfclient *client, char **nameservers, char *default_nameservers)
{
    char *ptr;
    int ret;

    debconf_get(client,"netcfg/get_nameservers");
    if (*nameservers)
        ptr = *nameservers;
    else if (strlen(client->value))
        ptr = client->value;
    else if (default_nameservers)
        ptr = default_nameservers;
    else
        ptr = "";
    debconf_set(client, "netcfg/get_nameservers", ptr);

    debconf_input(client, "critical", "netcfg/get_nameservers");
    ret = debconf_go(client);

    if (ret)
        return ret;

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

    if (*nameservers)
        free(*nameservers);
    *nameservers = NULL;
    if (ptr)
        *nameservers = strdup(ptr);

    return 0;
}

void netcfg_update_entropy (void)
{
#ifdef __linux__
    di_exec_shell("ip addr show >/dev/random");
#endif
}

/* Attempt to find out whether we've got link on an interface.  Don't try to
 * bring the interface up or down, we leave that to the caller.  Use a
 * progress bar so the user knows what's going on.  Return true if we got
 * link, and false otherwise.
 */
int netcfg_detect_link(struct debconfclient *client, const struct netcfg_interface *interface)
{
    char arping[256];
    int count, rv = 0;
    int link_waits = 12;  /* default for netcfg/link_wait_timeout times 4 */
    int gw_tries = NETCFG_GATEWAY_REACHABILITY_TRIES;
    const char *if_name = interface->name;
    const char *gateway = interface->gateway;

    if (!empty_str(gateway))
        sprintf(arping, "arping -c 1 -w 1 -f -I %s %s", if_name, gateway);

    /* Ask for link detection timeout. */
    int ok = 0;
    debconf_capb(client, "");

    while (!ok) {
        debconf_input(client, "low", "netcfg/link_wait_timeout");
        debconf_go(client);
        debconf_get(client, "netcfg/link_wait_timeout");

        char *ptr, *end_ptr;
        ptr = client->value;

        if (!empty_str(ptr)) {
            link_waits = strtol(ptr, &end_ptr, 10);
            /* The input contains a single positive integer. */
            if (*end_ptr == '\0' && link_waits > 0) {
                ok = 1;
                link_waits *= 4;
            }
        }

        if (!ok) {
            if (!empty_str(ptr)) {
                di_info("The value %s provided is not valid", ptr);
            }
            else {
                di_info("No value provided");
            }

            debconf_input (client, "critical", "netcfg/bad_link_wait_timeout");
            debconf_go (client);
            debconf_set(client, "netcfg/link_wait_timeout", "3");
        }
    }

    di_info("Waiting time set to %d", link_waits / 4);

    debconf_capb(client, "progresscancel");
    debconf_subst(client, "netcfg/link_detect_progress", "interface", if_name);
    debconf_progress_start(client, 0, link_waits, "netcfg/link_detect_progress");
    for (count = 0; count < link_waits; count++) {
        usleep(250000);
        if (debconf_progress_set(client, count) == CMD_PROGRESSCANCELLED) {
            /* User cancelled on us... bugger */
            rv = 0;
            di_info("Detecting link on %s was cancelled", if_name);
            break;
        }
        if (ethtool_lite(if_name) == 1) /* ethtool-lite's CONNECTED */ {
            di_info("Found link on %s", if_name);

            if (!empty_str(gateway) && !is_wireless_iface(if_name) && !is_layer3_qeth(interface)) {
                for (count = 0; count < gw_tries; count++) {
                    if (di_exec_shell_log(arping) == 0)
                        break;
                }
                di_info("Gateway reachable on %s", if_name);
            }

            rv = 1;
            break;
        }
    }

    if (count == link_waits) {
        di_info("Reached timeout for link detection on %s", if_name);
    }

    debconf_progress_stop(client);
    debconf_capb(client, "backup");

    return rv;
}

void netcfg_interface_init(struct netcfg_interface *iface)
{
    memset(iface, 0, sizeof(*iface));
    
    iface->name = NULL;
    iface->dhcp = -1;
    iface->dhcpv6 = -1;
    iface->address_family = -1;  /* I hope nobody uses -1 for AF_INET */
    iface->slaac = -1;
    iface->v6_stateful_config = -1;
    iface->v6_stateless_config = -1;
    iface->loopback = -1;
    iface->mode = MANAGED;
    iface->parentif = NULL;
    iface->vlanid = -1;
}

/* Parse an IP address (v4 or v6), with optional CIDR netmask, into
 * +interface+.  Return 1 if all went well, and return 0 if something
 * went wrong (the "IP address" wasn't, for example).  In the event
 * something went wrong, +interface+ is guaranteed to remain
 * unchanged.
 */
int netcfg_parse_cidr_address(const char *address, struct netcfg_interface *interface)
{
    struct in_addr addr;
    struct in6_addr addr6;
    int ok;
    char *maskptr, *addrstr, addrbuf[NETCFG_ADDRSTRLEN];
    int i;
    
    strncpy(addrbuf, address, NETCFG_ADDRSTRLEN-1);
    addrbuf[NETCFG_ADDRSTRLEN-1]='\0';
    addrstr = strtrim(addrbuf);
    
    if ((maskptr = strchr(addrstr, '/'))) {
        /* Houston, we have a netmask; split it into bits */
        *maskptr = '\0';
        maskptr++;

        /* Verify that the mask is OK */
        for (i = 0; maskptr[i]; i++) {
            if (!isdigit(maskptr[i])) {
                /* That's not good; bomb out early */
                return 0;
            }
        }
    }

    ok = inet_pton (AF_INET, addrstr, &addr);

    if (ok) {
        interface->address_family = AF_INET;
        inet_ntop(AF_INET, &addr, interface->ipaddress, INET_ADDRSTRLEN);
    } else {
        /* Potential IPv6 address */
        ok = inet_pton (AF_INET6, addrstr, &addr6);
        if (ok) {
            interface->address_family = AF_INET6;
            inet_ntop(AF_INET6, &addr6, interface->ipaddress, INET6_ADDRSTRLEN);
        }
    }

    if (ok && maskptr) {
        interface->masklen = atoi(maskptr);
    } else {
        interface->masklen = 0;
    }
        
    return ok;
}

void netcfg_network_address(const struct netcfg_interface *interface,
                            char *network)
{
    union inX_addr ipaddr, mask, net;
    
    inet_pton(interface->address_family, interface->ipaddress, &ipaddr);
    inet_mton(interface->address_family, interface->masklen, &mask);
    if (interface->address_family == AF_INET) {
    	net.in4.s_addr = ipaddr.in4.s_addr & mask.in4.s_addr;
    } else if (interface->address_family == AF_INET6) {
        int i;
        
        for (i = 0; i < 4; i++) {
            net.in6.s6_addr32[i] = ipaddr.in6.s6_addr32[i] & mask.in6.s6_addr32[i];
        }
    }
    	
    inet_ntop(interface->address_family, &net, network, NETCFG_ADDRSTRLEN);
}

void netcfg_broadcast_address(const struct netcfg_interface *interface,
                              char *broadcast)
{
    struct in_addr broad, net, mask;
    char network[INET_ADDRSTRLEN];
    
    /* IPv6 has no concept of broadcast addresses */
    if (interface->address_family != AF_INET) {
        broadcast[0] = '\0';
        return;
    }

    netcfg_network_address(interface, network);
    
    inet_pton(AF_INET, network, &net);
    inet_mton(AF_INET, interface->masklen, &mask);
    broad.s_addr = (net.s_addr | ~mask.s_addr);
    inet_ntop(AF_INET, &broad, broadcast, INET_ADDRSTRLEN);
}

/* Validate that the given gateway address actually lies within the given
 * network.  Standard boolean return.
 */
int netcfg_gateway_reachable(const struct netcfg_interface *interface)
{
    union inX_addr net, mask, gw_addr;
    char network[NETCFG_ADDRSTRLEN];
    
    netcfg_network_address(interface, network);
    
    inet_pton(interface->address_family, network, &net);
    inet_mton(interface->address_family, interface->masklen, &mask);
    inet_pton(interface->address_family, interface->gateway, &gw_addr);

    if (interface->address_family == AF_INET) {
        return (gw_addr.in4.s_addr && ((gw_addr.in4.s_addr & mask.in4.s_addr) == net.in4.s_addr));
    } else if (interface->address_family == AF_INET6) {
        int i;
        
        /* Check if the gateway is a link-local address (fe80::/64) */
        if (gw_addr.in6.s6_addr16[0] == htons(0xFE80) &&
                gw_addr.in6.s6_addr16[1] == 0 &&
                gw_addr.in6.s6_addr16[2] == 0 &&
                gw_addr.in6.s6_addr16[3] == 0) {
            return 1;
        }

        /* Check if the gateway is on the same subnet */
        for (i = 0; i < 4; i++) {
            if ((gw_addr.in6.s6_addr32[i] & mask.in6.s6_addr32[i]) != net.in6.s6_addr32[i]) {
                return 0;
            }
        }
        
        return 1;
    } else {
        /* Unknown address family */
        fprintf(stderr, "Unknown address family given to netcfg_gateway_unreachable\n");
        return 0;
    }
}

/* Take an FQDN (or possibly a bare hostname) and use it to preseed the get_hostname
 * and get_domain debconf variables.
 */
void preseed_hostname_from_fqdn(struct debconfclient *client, char *buf)
{
    char *dom;

    if (valid_domain(buf)) {
        di_debug("%s is a valid FQDN", buf);
        dom = strchr(buf, '.');
        if (dom) {
            di_debug("We have a real FQDN");
            *dom++ = '\0';
        }
                    
        debconf_set(client, "netcfg/get_hostname", buf);

        if (!have_domain && dom != NULL) {
            di_debug("Preseeding domain as well: %s", dom);
            debconf_set(client, "netcfg/get_domain", dom);
            have_domain = 1;
        } else if (have_domain && !empty_str(domain)) {
            /* Global var 'domain' is holding a temporary domain name,
             * presumably glommed from DHCP.  Use it as default instead.
             */
            di_debug("Preseeding domain from global: %s", domain);
            debconf_set(client, "netcfg/get_domain", domain);
        }
    }
}

/* Classic rtrim... strip off trailing whitespace from a string */
void rtrim(char *s)
{
	int n;
	
	n = strlen(s) - 1;
	
	while (n >= 0 && isspace(s[n])) {
		s[n] = '\0';
		n--;
	}
}

char *strtrim(char *s)
{
	size_t len;

	len = strlen(s);
	if (!len)
		return s;

	rtrim(s);

	while (*s && isspace(*s))
		s++;

	return s;
}

/* Check if /etc/resolv.conf symlinks to /run/resolvconf/resolv.conf. */
int is_resolvconf_used()
{
	int resolvconf_used = 0;

	char *path = realpath(RESOLV_FILE, NULL);

	if (path) {
		resolvconf_used = !strcmp(path, RESOLVCONF_FILE);
		free(path);
	}

	return resolvconf_used;
}

/* Check if /etc/resolv.conf symlinks to /run/systemd/resolve/stub-resolv.conf. */
int is_resolved_used()
{
	int resolved_used = 0;

	char *path = realpath(RESOLV_FILE, NULL);

	if (path) {
		resolved_used = !strcmp(path, RESOLVED_FILE);
		free(path);
	}

	return resolved_used;
}
