/*
 *   Copyright (c) 1998 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

static const char rcsid[] = "$Id: s.network.cc 1.17 02/05/04 01:28:30-07:00 root@egypt.pell.portland.or.us $";

/*
 * network: configure networking for some device
 */

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <string.h>
#include <ndialog.h>
#include <dialog.h>
#include <basis/validators.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 <basis/Nic.H>

#include "buttons.h"
#include "util.h"

char address[40];		/* network address */
int  netmaskgiven;		/* via address x.x.x.x/w */
char network[40];		/* network, if CIDR address not given */
char netmask[40];		/* netmask,    "   "   "  "  */
char gateway[40];		/* gateway address, if this is the default */
char isauto[40];		/* Autoconfigure this interface (dhcp,etc) */
char issystem[40];		/* Get DNS/YP settings from dhcp */
int  autoconf;			/* CONF= setting (index into conf[]) */
char autosystem;		/* flag for SYSTEM checkbutton */
char network_device[40];	/* the device we're configuring */

/* forward declarations */
static int chk_ipaddr(ndObject, ndDisplay);
static int chk_network(ndObject, ndDisplay);
static int chk_address(ndObject, ndDisplay);
static int chk_netmask(ndObject, ndDisplay);
static int chk_auto(ndObject, ndDisplay);
static int finalcheck(ndObject, ndDisplay);

extern "C" {
extern void Error(char *fmt, ...);
extern void safecopy(char *dest, char *src);
extern char *END(char *string);
}

static ListItem conf[] = {
    { "MANUAL", "Configure this device by hand",        "network.html#hand" },
#define CF_MANUAL	0
    { "DHCP", "Use dhcp/bootp for automatic configuration", "network.html#dhcp" },
#define CF_DHCP		1
    { "DISABLED",  "Do not configure this device",         "network.html#disabled" },
#define CF_DISABLED	2
};
#define NR_CONF	(sizeof conf / sizeof conf[0])


ndObject o_auto, o_system, o_address, o_network, o_netmask, o_gateway;


/*
 * newNicMenu() builds the object chain for the nic form
 */
ndObject
newNicMenu()
{
    ndObject chain;

    o_auto    = newMenu(11, 0,-1,1, NR_CONF, conf, 0, "", 0, chk_auto,
			"network.html#auto");
    setObjCursor(o_auto, autoconf);
    o_system  = newCheck( 8, 3, 0,
			 "|Get DNS and Yellow Pages automatically",
			 &autosystem, 0, "network.html#system");
    o_address = newString( 0, 4, 18, sizeof address, address,
		    0, "IP Address ", chk_address, "network.html#address");

    o_network = newString( 0, 7, 18, sizeof network, network,
		    0, "Network    ", chk_network, "network.html#network");

    o_netmask = newString(35, 7,18, sizeof netmask, netmask,
		    0, "Netmask    ", chk_netmask, "network.html#netmask");

    o_gateway = newString( 0,10,18, sizeof gateway, gateway,
		    0, "Gateway    ", chk_ipaddr, "network.html#gateway");

    chain = ObjChain(o_auto, o_system);
    chain = ObjChain(chain, o_address);
    chain = ObjChain(chain, o_network);
    chain = ObjChain(chain, o_netmask);
    chain = ObjChain(chain, o_gateway);

#if STAGE2
    chain = ObjChain(chain,
		newOKButton(1, "NEXT", finalcheck, "network.html"));
    chain = ObjChain(chain,
		newCancelButton(2, "BACK", finalcheck, "network.html"));
#else
    chain = ObjChain(chain,
		newOKButton(1, "  OK  ", finalcheck, "network.html"));
    chain = ObjChain(chain,
		newCancelButton(2, "CANCEL", 0, "network.html"));
#endif

    setReadonly(o_system);

    if (autoconf) {
	setReadonly(o_network);
	setReadonly(o_netmask);
	setReadonly(o_gateway);
	setReadonly(o_address);
    }
    else {
	if (!address[0]) {
	    setReadonly(o_network);
	    setReadonly(o_netmask);
	    setReadonly(o_gateway);
	}
    }

    return chain;
} /* newNicMenu */

/*
 * compute the network address from our ip address and the netmask
 */
static char *
make_network(char *address, char *netmask)
{
    int i1, i2, i3, i4;
    int n1, n2, n3, n4;
    static char bfr[80];

    i1 = i2 = i3 = i4 = 0;
    sscanf(address, "%d.%d.%d.%d", &i1, &i2, &i3, &i4);
    n1 = n2 = n3 = n4 = 0;
    sscanf(netmask, "%d.%d.%d.%d", &n1, &n2, &n3, &n4);

    sprintf(bfr, "%d.%d.%d.%d", i1 & n1, i2 & n2, i3 & n3, i4 & n4);
    return bfr;
} /* make_network */


/*
 * ethernetinventory() builds a nic inventory
 */
void
ethernetinventory(char *inventory)
{
    /* build an inventory list for the "changes are done" message
     */
    if (autoconf == CF_DISABLED) 
	sprintf(END(inventory), "%s is disabled\n", network_device);
    else if (autoconf > 0) {
	sprintf(END(inventory),
		    "The network address for %s is automatically\n"
		    "configured via %s.\n",
		    network_device, conf[autoconf].id);
    }
    else {
	if (strlen(address) > 0)
	    sprintf(END(inventory), "Address   : %s\n"
				    "Network   : %s\n"
				    "Netmask   : %s\n"
				    "\n",  address,
					   network,
					   netmask);
	if (strlen(gateway) > 0)
	    sprintf(END(inventory), "Gateway   : %s\n"
				    "\n", gateway);
    }
} /* ethernetinventory */


static int
finalcheck(ndObject dummy, ndDisplay display)
{
    int foo;
    char inventory[400];
    char msg[200];

    if (address[0] && isaddr(address, &foo) != 0) {
	Error("Invalid IP address");
	return 0;
    }

#if STAGE2
    return 1;
#else
    inventory[0] = 0;
    ethernetinventory(inventory);
    sprintf(msg, "Change network configuration for %s?", network_device);
    return dialog_mesgbox(msg, inventory, -1, -1) == 0;
#endif
} /* finalcheck */


/*
 * chk_auto() turns the appropriate fields on and off depending on the
 * settings of the autoconf flag
 */
static int
chk_auto(ndObject obj, ndDisplay display)
{
    autoconf = getObjCursor(obj);

    if (autoconf > 0) {
	setReadonly(o_system);

	setReadonly(o_address);
	setReadonly(o_network);
	setReadonly(o_netmask);
	setReadonly(o_gateway);
    }
    else {
	setReadonly(o_system);
	setWritable(o_address);
	if (address[0]) {
	    setWritable(o_network);
	    setWritable(o_netmask);
	    setWritable(o_gateway);
	}
    }
    drawObj(o_system, display);
    drawObj(o_address, display);
    drawObj(o_network, display);
    drawObj(o_netmask, display);
    drawObj(o_gateway, display);
    return 1;
} /* chk_auto */


/*
 * build a octet mask for a netmask width
 */
static int
bitmask(int bits)
{
    int mask = 0;

    switch (bits) {
    case 7: mask += 1;
    case 6: mask += 2;
    case 5: mask += 4;
    case 4: mask += 8;
    case 3: mask += 16;
    case 2: mask += 32;
    case 1: mask += 64;
    case 0: mask += 128;
    }

    return mask;
} /* bitmask */


static unsigned long
ipstrtolong(char *address)
{
    unsigned int a1, a2, a3, a4;

    sscanf(address, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);

    return (a1<<24L)|(a2<<16L)|(a3<<8L)|(a4);
}


/* Insert a default value for the netmask */
static int
chk_address(ndObject obj, ndDisplay display)
{
    int a;
    int bits = -1;
    char *p;
    unsigned long addr_long;
    unsigned long  net_long;
    unsigned long mask_long;

    if (!address[0]) {
	setReadonly(o_network);
	setReadonly(o_netmask);
	setReadonly(o_gateway);
	return 1;
    }
    p = isaddrc(address, &netmaskgiven);
    if (p) { Error(p); return 0; }

    setWritable(o_gateway);

    if (netmaskgiven) {
	bits = netmaskgiven-1;
	switch (bits / 8) {
	case 0: sprintf(netmask, "%d.0.0.0",       bitmask(bits%8));
		break;
	case 1: sprintf(netmask, "255.%d.0.0",     bitmask(bits%8));
		break;
	case 2: sprintf(netmask, "255.255.%d.0",   bitmask(bits%8));
		break;
	default:/* aka 3 */
		sprintf(netmask, "255.255.255.%d", bitmask(bits%8));
		break;
	}
	strcpy(network, make_network(address, netmask));
	setReadonly(o_network);
	setReadonly(o_netmask);
	return 1;
    }
    else {
	setWritable(o_network);
	setWritable(o_netmask);
    }

    /* If we changed our network address but stayed inside the
     * current network, don't bother to change the default
     * netmask
     */
    addr_long = ipstrtolong(address);
    net_long  = ipstrtolong(network);
    mask_long = ipstrtolong(netmask);

    if ((addr_long & mask_long) == net_long)
	return 1;

    if (sscanf(address, "%d", &a) == 1) {
	if (a > 0 && a < 128)
	    strcpy(netmask, "255.0.0.0");
	else if (a < 192)
	    strcpy(netmask, "255.255.0.0");
	else
	    strcpy(netmask, "255.255.255.0");
	strcpy(network, make_network(address, netmask));
	touchObj(o_network);
	touchObj(o_netmask);
	return 1;
    }
    return 0;
} /* chk_address */


/*
 * make sure an ipv4 address is okay
 */
static int
chk_ipaddr(ndObject obj, ndDisplay display)
{
    char *ip = (char*)objDataPtr(obj);
    int dummy;
    char *p;

    if (*ip == 0)
	return 1;

    if ((p =isaddr(ip, &dummy)) != (char*)0) {
	Error(p);
	return 0;
    }
    return 1;
} /* chk_ipaddr */


/*
 * make sure the network is okay
 */
static int
chk_network(ndObject obj, ndDisplay display)
{
    int dummy;
    char *p;

    if (*network == 0)
	return 1;

    if ((p =isquad(network, &dummy)) != (char*)0) {
	Error(p);
	return 0;
    }
    /* check the fourth octet to see if the network is
     * bogus -- (if octet & 0x1, then it may be bogus)
     * We only check it if it hasn't been written out
     * yet.
     */
    if (isDirty(obj)) {
	int octet;
	sscanf(network, "%*d.%*d.%*d.%d", &octet);
	if (octet & 0x1) {
	    Error("Is this a valid network?  Networks usually end in\n"
		  "zero (for class A, B, and C networks) or an even\n"
		  "number (for CIDR networks).  If this is a valid\n"
		  "network, just click through this entry;  I will\n"
		  "not ask you about it again.");
	    touchObj(obj);
	    return 0;
	}
    }
    return 1;
} /* chk_network */


/*
 * make sure the netmask is okay
 */
static int
chk_netmask(ndObject obj, ndDisplay display)
{
    int dummy;
    char *p;
    char *netmask = (char*)objDataPtr(obj);

    if (*netmask == 0)
	return 1;

    if ((p = isquad(netmask, &dummy)) != (char*)0) {
	Error(p);
	return 0;
    }
    
    strcpy(network, make_network(address, netmask));
    touchObj(o_network);
    return 1;
} /* chk_netmask */


/*
 * nicinit() populates the local fields from the given
 * Sysconfig
 */
void
nicinit(Nic &cf)
{
    char *p;

    if ((p = cf["CONF"]) != 0) {
	for (autoconf = NR_CONF-1; autoconf >= 0;  --autoconf)
	    if (streq(p, conf[autoconf].id))
		break;
    }
    else
	autoconf = -1;

    if (autoconf < 0)
#if STAGE2
	autoconf = CF_DHCP;
#else
	autoconf = CF_DISABLED;
#endif

    safecopy(address,   (char*)cf["ADDRESS"]);
    safecopy(network,   (char*)cf["NETWORK"]);
    safecopy(netmask,   (char*)cf["NETMASK"]);
    safecopy(gateway,   (char*)cf["GATEWAY"]);
    safecopy(issystem,   (char*)cf["SYSTEM"]);

    autosystem = (issystem[0] != 0);
} /* nicinit */


/*
 * niccommit() populates the given Sysconfig from the menu
 */
void
niccommit(Nic &cf)
{
    char *p;

    if (netmaskgiven) {
	/* rip off cidr width from address */
	p = (char*)strchr(address, '/');

	if (*p) *p = 0;
    }

    cf["CONF"] = conf[autoconf].id;
    cf.drop("SYSTEM");

    if (address[0]) {
	cf["ADDRESS"]    = address;
	cf["NETWORK"]    = network;
	cf["NETMASK"]    = netmask;
	cf["GATEWAY"]    = gateway;
    }
    else {
	cf.drop("ADDRESS");
	cf.drop("NETWORK");
	cf.drop("NETMASK");
	cf.drop("GATEWAY");
    }
    cf.commit();
} /* niccommit */


#if STAGE2
int
nicmenu()
{
    int rc;
    ndObject layout = newNicMenu();

    rc = MENU(layout,
		-1, -1,
		"Configure network address", 0, 0);
    deleteObjChain(layout);


    switch (rc) {
    case MENU_CANCEL:
    case MENU_ESCAPE:	return BACK_BUTTON;
    case MENU_OK:	return NEXT_BUTTON;
    default:		return ERROR_BUTTON;
    }
} /* nicmenu */

#else

/*
 * device_callback() sets network_device from a menu selection
 */
static int
device_callback(ndObject obj, ndDisplay display)
{
    ListItem* list = getObjList(obj);
    int cursor     = getObjCursor(obj);

    strcpy(network_device, list[cursor].item);
    return -1;
} /* device_callback */


/*
 * getNetDevice() picks up an ethernet device from looking at
 * /proc/net/dev and letting the user choose.
 */
int
getNetDevice()
{
    LIA devices = newLIA(0,0);
    FILE *f;
    ndObject chain;
    int rc;

#ifdef __linux
    char bfr[1024];
    char *p;
    char *dev;

    if ((f = fopen("/proc/net/dev", "r")) != 0) {
	while (fgets(bfr, sizeof bfr-1, f)) {
	    for (dev = bfr; isspace(*dev); ++dev) ;
	    for (p = dev; isalnum(*p); ++p) ;
	    if (*p != ':')
		continue;
	    *p = 0;
	    if (!streq(dev, "lo"))
		addToLIA(devices, 0, dev, 0);
	}
	fclose(f);
    }
#endif /*__linux*/

    if (LIAcount(devices) == 0) {
aaiiee:
	Error("No network devices in the system");
	deleteLIA(devices);
	return 0;
    }
    else if (LIAcount(devices) == 0) {
	strcpy(network_device, LIAlist(devices)[0].item);
	deleteLIA(devices);
	return 1;
    }

    chain = ObjChain(newMenu(10,0,-1,
			     LIAcount(devices) > 5 ? 5 : LIAcount(devices),
			     LIAcount(devices),LIAlist(devices),
			     0,"",0,device_callback, "network.html#choose"),
		     newCancelButton(0, "Cancel", 0, "network.html"));

    rc = MENU(chain, -1, -1, "Pick a device to configure", 0, 0);

    deleteObjChain(chain);
    deleteLIA(devices);

    return (rc == MENU_OK) ? 1 : 0;
} /* getNetDevice */

extern "C" {
/*
 * doit actually splashes up the menus and gathers information
 */
int
doit(int argc, char **argv)
{
    if (argc < 2) {
	if (getNetDevice() == 0)
	    return 0;
    }
    else
	strcpy(network_device, argv[1]);

    Nic cf(network_device);
    int rc;
    char bfr[400];
    char *p;

    if (!cf.isvalid()) {
	sprintf(bfr, "Network device %s does not\nexist; configure anyway?",
			network_device);
	if (dialog_yesno("Error!", bfr, -1, -1) != 0)
	    return 1;
    }

    nicinit(cf);

    ndObject layout = newNicMenu();


    sprintf(bfr, "Configure %s", network_device);
    if ( (rc = MENU(layout, -1, -1, bfr, 0, 0)) == MENU_OK )
	niccommit(cf);

    deleteObjChain(layout);

    return (rc==MENU_OK) ? 0 : 1;
} /* doit */
} /* extern "C" */

#endif
