/*
 * resolver: edit /etc/resolv.conf
 *
 *   Copyright (c) 1996 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.resolver.cc 1.19 01/02/08 11:27:12-08:00 orc@downbelow.pell.portland.or.us $";

/*
 * ABANDON ALL HOPE, YE WHO ENTER HERE
 */

/*
 * what the screen should look like:
 *
 *  +---------------+
 *  |               |
 *  | DOMAIN        |
 *  |               |
 *  | NAMESERVERS   |
 *  |               |
    |[options][y][n]|
 *  +---------------+
 */

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <curses.h>
#include <ndialog.h>
#include <dialog.h>
#include <basis/validators.h>
}

#include <basis/Hosts.H>
#include <basis/HostConf.H>
#include <basis/Sysconfig.H>
#include <basis/Resolv.H>

#include "buttons.h"

static char opt_debug;
static char ndots[40];
static int opt_ndots;
static int where_ndots;

#ifndef MAXNS
# define MAXNS	6
#endif
#define MAXDOM	6
#define MAXSORT	10
#define MAXORDER 3

#if MAXNS > 4
# define NSMENU 4
#else
# define NSMENU MAXNS
#endif

static int mayusedomain;

/* validator callbacks */
extern int add_domain(ndObject obj, ndDisplay display);
extern int add_ns(ndObject obj, ndDisplay display);
extern int add_sort(ndObject obj, ndDisplay display);
extern int chk_order(ndObject obj, ndDisplay display);
extern int chk_ndots(ndObject obj, ndDisplay display);

/* last chance check for validation */
static int finalcheck(ndObject obj, ndDisplay display);
static int resolver_options(ndObject obj, ndDisplay display);

void resolverinventory(char *inventory);

extern "C" {
#include "util.h"
}

static ListItem order[] = {
    {(char*) 0, "hosts", },
    {(char*) 1, "hosts,bind" },
    {(char*) 2, "hosts,nis" },
    {(char*) 3, "hosts,bind,nis" },
    {(char*) 4, "hosts,nis,bind" },
    {(char*) 5, "bind", },
    {(char*) 6, "bind,nis" },
    {(char*) 7, "bind,hosts" },
    {(char*) 8, "bind,nis,hosts" },
    {(char*) 9, "bind,hosts,nis" },
    {(char*)10, "nis" },
    {(char*)11, "nis,bind" },
    {(char*)12, "nis,hosts" },
    {(char*)13, "nis,hosts,bind" },
    {(char*)14, "nis,bind,hosts" },
    {(char*)15, "THIS SPACE FOR RENT" },
};
#define NRORDER	(sizeof order/sizeof order[0])

static char resorder[100];

static LIA domains;
static LIA sortlist;
static LIA nameservers;

static KA *sort;

/* 
 * Two layouts; one is the main resolver layout that allows rudimentary
 * adding and deletion of items from your domain or nameserver list, and
 * the other being options, so the adventurous can set ndots, sortlist,
 * and debug
 */


/*
 * newResolvMenu() generates the one for modifying your domain and/or
 *                 nameserver list
 */
ndObject lo_domain, lo_ns, lo_opt;

ndObject
newResolvMenu()
{
    ndObject chain;

    lo_domain = newListWidget(0,0,25,6,domains,"Domains", add_domain, 0, 0, "resolv.html#domains");

    lo_ns = newListWidget(40,0,20,6, nameservers, "Nameservers", add_ns, 0, 0, "resolv.html#nameserver");

    lo_opt = newButton(2, "OPTIONS", resolver_options, "resolv.html#opts");

    chain = ObjChain(lo_domain, lo_ns);
    chain = ObjChain(chain, lo_opt);

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

    return chain;
} /* newResolvMenu */


/*
 * newOptionsMenu() generates the one for setting nameserver options
 */
ndObject oo_addsort, oo_sortlist, oo_resorder;

ndObject
newOptionsMenu()
{
    ndObject chain;

    sortlist = newLIA(0,0);

    chain = ObjChain(newString(0, 0, 10, sizeof ndots, ndots, 0,
				    "ndots", chk_ndots, "resolv.html#ndots"),
		     newCheck(27,1,0,"|enable resolver debugging",
				    &opt_debug, 0, "resolv.html#debugging"));

    oo_resorder = newRadioList(4, 4, -1, 5, NRORDER-1, order, 
				    "search order", "", HIGHLIGHT_SELECTED,
				    0, "resolv.html#search");
    oo_sortlist = newListWidget(27,4,28,6,sortlist, "Sort list", add_sort, 0, 0, "resolv.html#sortlist");

    chain = ObjChain(chain, oo_resorder);
    chain = ObjChain(chain, oo_sortlist);

    chain = ObjChain(chain, newOKButton(1, "  OK  ", 0, 0));
    chain = ObjChain(chain, newCancelButton(2, "CANCEL", 0, 0));

    return chain;
} /* newOptionsMenu */


/*
 * final check before committing changes
 */
static int
finalcheck(ndObject button, ndDisplay display)
{
#if STAGE2
    return 1;
#else
    char inventory[800];
    /* build an inventory list for the "changes are done" message
     */
    inventory[0] = 0;

    resolverinventory(inventory);

    return dialog_yesno((unsigned char*) "Change DNS Settings?",
			(unsigned char*) inventory, -1, -1) == 0;
#endif
} /* finalcheck */


/*
 * resolverinventory() shows what your resolver settings are.
 */
void
resolverinventory(char *inventory)
{
    if (mayusedomain && LIAcount(domains) == 1)
	sprintf(END(inventory), "Domain : %s\n", LIAlist(domains)[0].item);
    else if (LIAcount(domains) > 0) {
	int i;
	int xp = 9;	/* strlen("Search : "); */

	sprintf(END(inventory), "Search : ");
	for (i=0; i< LIAcount(domains); i++) {
	    if (xp + strlen(LIAlist(domains)[i].item) > 60) {
		xp = 9;
		strcat(inventory, "\n        "); /* 9 spaces for alignment */
	    }
	    sprintf(END(inventory), " %s", LIAlist(domains)[i].item);
	}
	strcat(inventory, "\n");
    }

    if (LIAcount(nameservers) == 0)
	strcat(inventory, "\nNo nameservers defined\n");
    else {
	char *prompt = "Nameservers:";
	int i;

	for (i=0; i<LIAcount(nameservers); i++) {
	    sprintf(END(inventory), "%s %s\n", prompt, LIAlist(nameservers)[i].item);
	    prompt   = "            ";	/* same length as "Nameservers:" */
	}
    }

    if (opt_debug || opt_ndots != 1 || LIAcount(sortlist))
	strcat(inventory, "\n");

    sprintf(END(inventory), "Search order: %s\n", resorder);
    
    if (opt_debug || opt_ndots != 1) {
	strcat(inventory, "Options:");
	if (opt_debug)
	    strcat(inventory, " debug");
	if (opt_ndots > 1)
	    sprintf(END(inventory), " ndots:%d", opt_ndots);
	strcat(inventory, "\n");
    }
    if (sort->count() > 0) {
	char *prompt = "Sort list:";

	for (unsigned int i=0; i < sort->count(); i++) {
	    sprintf(END(inventory), "%s %s\n", prompt, (char*)((*sort)[i]));
	    prompt   = "          ";	/* same length as "Sort list:" */
	}
    }
} /* resolverinventory */


/*
 * squeeze() pulls the leading and trailing blanks off a string
 */
static void
squeeze(char *string)
{
    char *p = string, *q = string;

    while (isspace(*q))
	++q;

    while (*q != 0)
	*p++ = *q++;

    while (p > string && isspace(p[-1]))
	--p;

    *p = 0;
} /* squeeze */


/*
 * chk_ndots() makes sure that ndots() is a valid number or blank
 */
int
chk_ndots(ndObject obj, ndDisplay display)
{
    char *ndots = (char*)objDataPtr(obj);
    char *p;

    squeeze(ndots);
    if (*ndots == 0) {
	opt_ndots = 1;
	return 1;
    }
    for (p=ndots; *p; ++p)
	if (!isdigit(*p)) {
	    Error("ndots must be a number");
	    return 0;
	}
    opt_ndots = atoi(ndots);
    return 1;
} /* chk_ndots */


/*
 * add_ns() cleans up the new nameserver name and makes sure it's unique
 * before adding it to the list of nameservers.
 */
int
add_ns(ndObject obj, ndDisplay display)
{
    int x;
    char *newns = (char*)objDataPtr(obj);

    squeeze(newns);

    if (*newns == 0)
	return 0;

    for (x=0; x < LIAcount(nameservers); x++)
	if (strcasecmp(newns, LIAlist(nameservers)[x].item) == 0) {
	    delFromLIA(nameservers, x);
	    --x;
	}
    return 1;
} /* add_ns */


/*
 * add_domain() adds a domain to the domain list
 */
int
add_domain(ndObject obj, ndDisplay display)
{
    int x;
    char *newdom = (char*)objDataPtr(obj);

    squeeze(newdom);

    if (*newdom == 0)
	return 0;

    for (x=0; x < LIAcount(domains); x++)
	if (strcasecmp(newdom, LIAlist(domains)[x].item) == 0) {
	    delFromLIA(domains, x);
	    --x;
	}
    return 1;
} /* add_domain */


/*
 * add_sort() adds an domain to the sort list
 */
int
add_sort(ndObject obj, ndDisplay display)
{
    char *dom = (char*)objDataPtr(obj);
    int x;
    char *m, *p;

    squeeze(dom);
    if (*dom == 0)
	return 0;

    if ((m = isaddr(dom, &x)) != 0) {
	Error("%s", m);
	return 0;
    }

    if ((p = (char*)strchr(dom, '/')) != 0 && (m = isquad(1+p, &x)) != 0) {
	Error("%s", m);
	return 0;
    }
    for (x=0; x < LIAcount(sortlist); x++)
	if (strcasecmp(dom, LIAlist(sortlist)[x].item) == 0) {
	    delFromLIA(sortlist, x);
	    --x;
	}
    return 1;
} /* add_sort */


/*
 * options() runs the menu for the OPTIONS button
 */
static int
resolver_options(ndObject foo, ndDisplay display)
{
    /* we populate the various options every time we come into this
     * menu, then populate back if the result is okay.
     */
    unsigned int i;
    int rc;
    int debug_is = opt_debug;
    int ndots_is = opt_ndots;
    ndObject chain;

    chain = newOptionsMenu();

    while (LIAcount(sortlist) > 0)
	delFromLIA(sortlist, 0);
    for (i=0; i < sort->count(); i++)
	addToLIA(sortlist, 0, (char*)((*sort)[i]), 0);

    for (i=0; i<NRORDER; i++)
	order[i].selected = 0;
    for (i=0; i<NRORDER-1; i++)
	if (strcasecmp(order[i].item, resorder) == 0)
	    break;
    if (i == NRORDER-1) {
	order[i].item = resorder;
	setObjData(oo_resorder, 0L, order, NRORDER);
    }
    else
	setObjData(oo_resorder, 0L, order, NRORDER-1);
    order[i].selected = 1;

    setObjCursor(oo_resorder, i);

    rc = MENU(chain, -1, -1, "DNS options", 0, 0);

    if (rc == MENU_OK) {
	/* copy in the new sortlist
	 */
	(*sort) = 0;	/* zero out the old */

	for (int ix=0; ix < LIAcount(sortlist); ix++)
	    (*sort) += LIAlist(sortlist)[ix].item;

	if (currentSelection(oo_resorder) != NRORDER-1)
	    strcpy(resorder, order[currentSelection(oo_resorder)].item);

	strcpy(resorder, getObjList(oo_resorder)[currentSelection(oo_resorder)].item);
    }
    else {
	opt_debug = debug_is;
	opt_ndots = ndots_is;
    }

    deleteObjChain(chain);
    deleteLIA(sortlist);

    return 0;
} /* options */


/*
 * resolverinit() populates the resolver menu from the given Resolv
 */
void
resolverinit(Resolv &resolv, HostConf &host)
{
    mayusedomain = 0;

    domains = newLIA(0,0);
    nameservers = newLIA(0,0);

    if (resolv["DOMAIN"].count() > 0) {
	/* resolv.conf uses DOMAIN instead of SEARCH */
	mayusedomain = 1;
	addToLIA(domains, 0, resolv["DOMAIN"][0], 0);
    }
    else if (resolv["SEARCH"].count() > 0) {
	unsigned int i;

	for (i=0; i < resolv["SEARCH"].count(); i++)
	    addToLIA(domains, 0, resolv["SEARCH"][i], 0);

    }

    if (resolv["NAMESERVER"].count() > 0) {
	unsigned int i;

	for (i=0; i < resolv["NAMESERVER"].count(); i++)
	    addToLIA(nameservers, 0, resolv["NAMESERVER"][i], 0);
    }

    /* get debugging options */
    memset(ndots, 0, sizeof ndots);
    opt_ndots = 1;
    if (resolv["DEBUG"].count() > 0) {
	opt_debug = (resolv["DEBUG"].locate("debug") >= 0);

	unsigned int i;

	where_ndots = EOF;
	for (i = 0; i < resolv["DEBUG"].count(); i++)
	    if (strncasecmp(resolv["DEBUG"][i], "ndots:", 5) == 0) {
		strcpy(ndots, 5 + (char*)resolv["DEBUG"][i]);
		opt_ndots = atoi(ndots);
		where_ndots = i;
		break;
	    }
    }

    sort = &(resolv["SORTLIST"]);

#if STAGE2
    strcpy(resorder, "hosts,bind");
#else
    if (host.has("ORDER"))
	strcpy(resorder, host["ORDER"]);
    else
	resorder[0] = 0;
#endif
} /* resolverinit */


/*
 * resolvercommit() populates the given Resolv from our menu
 */
void
resolvercommit(Resolv& resolv, HostConf& host)
{
    int i;

    if (mayusedomain && LIAcount(domains) == 1) {
	/* If resolv.conf has a DOMAIN options, leave it there if
	 * only one domain is listed, otherwise replace it with a
	 * SEARCH option or nothing at all
	 */
	resolv.create("SEARCH");
	resolv["DOMAIN"] = LIAlist(domains)[0].item;
    }
    else {
	resolv.create("DOMAIN");
	resolv.create("SEARCH");

	for (i=0; i<LIAcount(domains); i++)
	    resolv["SEARCH"] += LIAlist(domains)[i].item;
    }
    if (!opt_debug)
	resolv["DEBUG"].drop("debug");
    if (where_ndots >= 0)
	resolv["DEBUG"].drop(resolv["DEBUG"][where_ndots]);

    if (opt_ndots != 1) {
	char ndots_str[20];
	sprintf(ndots_str, "ndots:%d", opt_ndots);
	resolv["DEBUG"] += ndots_str;
    }

    host["ORDER"] = resorder;

    resolv.drop("NAMESERVER");
    for (i=0; i<LIAcount(nameservers); i++)
	resolv["NAMESERVER"] += LIAlist(nameservers)[i].item;

    resolv.commit();
    host.commit();
} /* resolvercommit */


#if STAGE2

int
resolvermenu()
{
    int rc;
    ndObject chain;

    chain = newResolvMenu();
    rc = MENU(chain, -1, -1, "DNS configuration", 0, 0);
    deleteObjChain(chain);

    if (rc == MENU_OK)
	return NEXT_BUTTON;
    else if (rc == MENU_ESCAPE || rc == MENU_CANCEL)
	return BACK_BUTTON;
    else
	return ERROR_BUTTON;
} /* resolvermenu */

#else

extern "C" {
/*
 * doit actually splashes up the menus and gathers information
 */
int
doit()
{
    Resolv   resolv;
    HostConf host;
    int rc;
    ndObject chain;

    if (resolv["MAGIC"].count() && strcasecmp(resolv["MAGIC"][0], "cookie") == 0) { 
	rc = dialog_yesno("WARNING!", "The resolver settings were made by\n"
				      "the system.  Are you sure you want\n"
				      "to modify these resolver settings\n"
				      "(the changes will be lost the next\n"
				      "time the system reboots)?",
				      -1,-1);
	if (rc != 0)
	    return 1;
    }

    resolverinit(resolv,host);
    chain = newResolvMenu();

    if ( (rc = MENU(chain, -1, -1, "DNS settings", 0, 0)) == MENU_OK )
	resolvercommit(resolv,host);

    deleteObjChain(chain);

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

#endif
