/* 
 * Net: the interface to a network configuration file
 *
 * Copyright (c) 1996 David L Parsons
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */
static const char rcsid[] = "$Id: s.Nic.cc 1.8 98/06/23 21:53:37-00:00 orc $";

extern "C" {
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef __linux
# include <linux/if.h>
# include <linux/if_arp.h>
# include <linux/if_ether.h>
#endif

#include <netdb.h>
}

#include "Nic.H"
#include "util.H"

/*
 * _if_found returns TRUE if the interface in question exists
 */
int
Nic::_if_found(char *ifname)
{
#ifdef __linux__
    int iffd;
    int rc;
    struct ifreq ifr;

    /* open a socket into the inet service, so we can browse for interfaces */
    if ((iffd = socket(AF_INET, SOCK_DGRAM,0)) < 0)
    	return 0;

    /* attempt to grab the flags for this interface */
    strcpy(ifr.ifr_name, ifname);
    rc = ioctl(iffd, SIOCGIFFLAGS, &ifr);
    close(iffd);

    return (rc == 0);
#else
    return 1;
#endif
} /* _if_found */


/*
 * load up /etc/rc.d/net.cf/{device}, or suitable defaults
 */
Nic::Nic(char *device):Karray()
{
    int rc;

    _valid = _found = 0;

    if ((_device = strdup(device)) == 0)
	return;

    _valid = _if_found(device);

    if ((rc = Refresh()) == EOF) {
	/* set up a template array if we can't find a configuration
	 * file.  Almost no defaults can be found, so in this case
	 * the user had better do something or else they will be
	 * unpleasantly surprised when the next reboot comes around
	 */
	_found = 0;
	create("CONF");
	create("ASKSERVER");
	create("SYSTEM");
	create("ADDRESS");
	create("NETWORK");
	create("NETMASK");
	create("BROADCAST");
	create("GATEWAY");
    }
    else
	_found = 1;
    changed(0);
} /* Nic::Nic(char *device) */



/*
 * Attach a Nic to the first non-loopback device we can find.
 */
Nic::Nic():Karray()
{
    struct ifreq *ifr;
    struct ifconf ifc;
    char bfr[1024];
    int rc;
    int iffd;

    _valid = _found = 0;

    /* open a socket into the inet service, so we can browse for interfaces */

    if ((iffd = socket(AF_INET, SOCK_DGRAM,0)) < 0)
    	return;

    ifc.ifc_len = sizeof bfr;
    ifc.ifc_buf = bfr;

    /* pull a list of all of the interfaces out of the inet service */

    rc = ioctl(iffd, SIOCGIFCONF, &ifc);
    close(iffd);

    if (rc != 0)
	return;

    /* walk the interface list, looking for the first one */

    ifr = ifc.ifc_req;
    for (int i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)
	if (strcmp(ifr->ifr_name, "lo") != 0 && _if_found(ifr->ifr_name)) {
	    Nic(ifr->ifr_name);
	    return;
	}
    /* if we get here, there are no interfaces to be found :-( */
} /* Nic::Nic() */


/*
 * ~Nic():  die
 */
Nic::~Nic()
{
    if (_device)
	free(_device);
} /* Nic::~Nic */


/*
 * if anything has changed, write it to disk.
 * 
 * TODO: file locking, dirty file checking.
 */
int
Nic::commit()
{
    FILE *cf;
    char path[sizeof NET_DIR + 114];
    char runlevel;

    if (changed()) {
#if ACTUAL
	runlevel = getpid() ? '0' : Runlevel();
	if (runlevel == MULTIUSER) {
	    sprintf(path, "/etc/rc.d/init.d/network stop %s", device());
	    system(path);
	}
#endif

	sprintf(path, NET_DIR "/%s", device());
	if (backup(path) != 0)
	    return EOF;
	if ((cf = fopen(path, "w")) == (FILE*)0)
	    return EOF;
	make_header(cf, 0);
	print(cf);
	fclose(cf);

#if ACTUAL
	if (runlevel == MULTIUSER) {
	    sprintf(path, "/etc/rc.d/init.d/network start %s", device());
	    system(path);
	}
#endif
	changed(0);
    }
    return 0;
} /* Nic::commit */


/*
 * has the disk configuration file been touched since we last loaded it in?
 */
int
Nic::dirty()
{
    return 0;
}

/*
 * Refresh the configuration from the disk image 
 */
int
Nic::Refresh()
{
    FILE *cf;
    char path[sizeof NET_DIR + 14];

    sprintf(path, NET_DIR "/%s", device());
    if ((cf = fopen(path, "r")) != (FILE*)0) {
	Erase();		/* never merge configurations */
	read(cf);
	fclose(cf);
	return 0;
    }
    changed(0);
    return EOF;
} /* Nic::Refresh */
